Merge "Add SystemUI-tests-utils filegroup to reuse in downstream"
diff --git a/api/current.txt b/api/current.txt
index 2e099ed..8522034 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2812,12 +2812,12 @@
method public void onClicked(android.accessibilityservice.AccessibilityButtonController);
}
- public final class AccessibilityGestureInfo implements android.os.Parcelable {
+ public final class AccessibilityGestureEvent implements android.os.Parcelable {
method public int describeContents();
method public int getDisplayId();
method public int getGestureId();
method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.accessibilityservice.AccessibilityGestureInfo> CREATOR;
+ field @NonNull public static final android.os.Parcelable.Creator<android.accessibilityservice.AccessibilityGestureEvent> CREATOR;
}
public abstract class AccessibilityService extends android.app.Service {
@@ -2836,7 +2836,7 @@
method public abstract void onAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
method public final android.os.IBinder onBind(android.content.Intent);
method @Deprecated protected boolean onGesture(int);
- method public boolean onGesture(@NonNull android.accessibilityservice.AccessibilityGestureInfo);
+ method public boolean onGesture(@NonNull android.accessibilityservice.AccessibilityGestureEvent);
method public abstract void onInterrupt();
method protected boolean onKeyEvent(android.view.KeyEvent);
method protected void onServiceConnected();
@@ -23968,6 +23968,7 @@
method @Nullable public long[] getThumbnailRange();
method public boolean hasAttribute(@NonNull String);
method public boolean hasThumbnail();
+ method public static boolean isSupportedMimeType(@NonNull String);
method public boolean isThumbnailCompressed();
method public void saveAttributes() throws java.io.IOException;
method public void setAttribute(@NonNull String, @Nullable String);
@@ -53040,6 +53041,7 @@
method @NonNull public android.view.autofill.AutofillId newAutofillId(@NonNull android.view.autofill.AutofillId, long);
method @NonNull public final android.view.ViewStructure newViewStructure(@NonNull android.view.View);
method @NonNull public final android.view.ViewStructure newVirtualViewStructure(@NonNull android.view.autofill.AutofillId, long);
+ method public final void notifySessionLifecycle(boolean);
method public final void notifyViewAppeared(@NonNull android.view.ViewStructure);
method public final void notifyViewDisappeared(@NonNull android.view.autofill.AutofillId);
method public final void notifyViewTextChanged(@NonNull android.view.autofill.AutofillId, @Nullable CharSequence);
diff --git a/api/system-current.txt b/api/system-current.txt
index 4859d54..c4c01af 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -8242,6 +8242,7 @@
method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannelBySlot(int, @Nullable String, int);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduBasicChannelBySlot(int, int, int, int, int, int, @Nullable String);
method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduLogicalChannelBySlot(int, int, int, int, int, int, int, @Nullable String);
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApplicationOnUicc(int);
method public boolean isDataConnectivityPossible();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isEmergencyAssistanceEnabled();
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isIdle();
@@ -9516,7 +9517,8 @@
method public void onReady();
method public final void onSendSmsResult(int, int, int, int) throws java.lang.RuntimeException;
method public final void onSmsReceived(int, String, byte[]) throws java.lang.RuntimeException;
- method public final void onSmsStatusReportReceived(int, int, String, byte[]) throws java.lang.RuntimeException;
+ method @Deprecated public final void onSmsStatusReportReceived(int, int, String, byte[]) throws java.lang.RuntimeException;
+ method public final void onSmsStatusReportReceived(int, String, byte[]) throws java.lang.RuntimeException;
method public void sendSms(int, int, String, String, boolean, byte[]);
field public static final int DELIVER_STATUS_ERROR_GENERIC = 2; // 0x2
field public static final int DELIVER_STATUS_ERROR_NO_MEMORY = 3; // 0x3
diff --git a/api/test-current.txt b/api/test-current.txt
index 6e28f67..3718c4c 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -34,8 +34,8 @@
package android.accessibilityservice {
- public final class AccessibilityGestureInfo implements android.os.Parcelable {
- ctor public AccessibilityGestureInfo(int, int);
+ public final class AccessibilityGestureEvent implements android.os.Parcelable {
+ ctor public AccessibilityGestureEvent(int, int);
}
}
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index c9a4b3b..43058d5 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -51,11 +51,6 @@
srcs: [
":statsd_aidl",
"src/active_config_list.proto",
- "src/statsd_config.proto",
- "src/uid_data.proto",
- "src/FieldValue.cpp",
- "src/hash.cpp",
- "src/stats_log_util.cpp",
"src/anomaly/AlarmMonitor.cpp",
"src/anomaly/AlarmTracker.cpp",
"src/anomaly/AnomalyTracker.cpp",
@@ -63,51 +58,56 @@
"src/anomaly/subscriber_util.cpp",
"src/condition/CombinationConditionTracker.cpp",
"src/condition/condition_util.cpp",
- "src/condition/SimpleConditionTracker.cpp",
"src/condition/ConditionWizard.cpp",
+ "src/condition/SimpleConditionTracker.cpp",
"src/condition/StateConditionTracker.cpp",
"src/config/ConfigKey.cpp",
"src/config/ConfigListener.cpp",
"src/config/ConfigManager.cpp",
"src/external/GpuStatsPuller.cpp",
"src/external/Perfetto.cpp",
- "src/external/StatsPuller.cpp",
+ "src/external/PowerStatsPuller.cpp",
+ "src/external/puller_util.cpp",
+ "src/external/ResourceHealthManagerPuller.cpp",
"src/external/StatsCallbackPuller.cpp",
"src/external/StatsCompanionServicePuller.cpp",
- "src/external/SubsystemSleepStatePuller.cpp",
- "src/external/PowerStatsPuller.cpp",
- "src/external/ResourceHealthManagerPuller.cpp",
- "src/external/TrainInfoPuller.cpp",
+ "src/external/StatsPuller.cpp",
"src/external/StatsPullerManager.cpp",
- "src/external/puller_util.cpp",
+ "src/external/SubsystemSleepStatePuller.cpp",
+ "src/external/TrainInfoPuller.cpp",
+ "src/FieldValue.cpp",
+ "src/guardrail/StatsdStats.cpp",
+ "src/hash.cpp",
+ "src/HashableDimensionKey.cpp",
"src/logd/LogEvent.cpp",
"src/logd/LogEventQueue.cpp",
"src/matchers/CombinationLogMatchingTracker.cpp",
"src/matchers/EventMatcherWizard.cpp",
"src/matchers/matcher_util.cpp",
"src/matchers/SimpleLogMatchingTracker.cpp",
- "src/metrics/MetricProducer.cpp",
- "src/metrics/EventMetricProducer.cpp",
"src/metrics/CountMetricProducer.cpp",
- "src/metrics/DurationMetricProducer.cpp",
- "src/metrics/duration_helper/OringDurationTracker.cpp",
"src/metrics/duration_helper/MaxDurationTracker.cpp",
- "src/metrics/ValueMetricProducer.cpp",
+ "src/metrics/duration_helper/OringDurationTracker.cpp",
+ "src/metrics/DurationMetricProducer.cpp",
+ "src/metrics/EventMetricProducer.cpp",
"src/metrics/GaugeMetricProducer.cpp",
- "src/metrics/MetricsManager.cpp",
+ "src/metrics/MetricProducer.cpp",
"src/metrics/metrics_manager_util.cpp",
+ "src/metrics/MetricsManager.cpp",
+ "src/metrics/ValueMetricProducer.cpp",
"src/packages/UidMap.cpp",
- "src/storage/StorageManager.cpp",
+ "src/shell/shell_config.proto",
+ "src/shell/ShellSubscriber.cpp",
+ "src/socket/StatsSocketListener.cpp",
+ "src/stats_log_util.cpp",
+ "src/statscompanion_util.cpp",
+ "src/statsd_config.proto",
"src/StatsLogProcessor.cpp",
"src/StatsService.cpp",
- "src/statscompanion_util.cpp",
+ "src/storage/StorageManager.cpp",
"src/subscriber/IncidentdReporter.cpp",
"src/subscriber/SubscriberReporter.cpp",
- "src/HashableDimensionKey.cpp",
- "src/guardrail/StatsdStats.cpp",
- "src/socket/StatsSocketListener.cpp",
- "src/shell/ShellSubscriber.cpp",
- "src/shell/shell_config.proto",
+ "src/uid_data.proto",
],
local_include_dirs: [
@@ -120,23 +120,23 @@
],
shared_libs: [
- "libbase",
- "libbinder",
- "libgraphicsenv",
- "libincident",
- "liblog",
- "libutils",
- "libservices",
- "libprotoutil",
- "libstatslog",
- "libhidlbase",
"android.frameworks.stats@1.0",
"android.hardware.health@2.0",
+ "android.hardware.power.stats@1.0",
"android.hardware.power@1.0",
"android.hardware.power@1.1",
- "android.hardware.power.stats@1.0",
- "libsysutils",
+ "libbase",
+ "libbinder",
"libcutils",
+ "libgraphicsenv",
+ "libhidlbase",
+ "libincident",
+ "liblog",
+ "libprotoutil",
+ "libservices",
+ "libstatslog",
+ "libsysutils",
+ "libutils",
],
}
@@ -210,54 +210,54 @@
"src/atom_field_options.proto",
"src/atoms.proto",
- "src/stats_log.proto",
"src/shell/shell_data.proto",
+ "src/stats_log.proto",
"tests/AlarmMonitor_test.cpp",
"tests/anomaly/AlarmTracker_test.cpp",
"tests/anomaly/AnomalyTracker_test.cpp",
- "tests/ConfigManager_test.cpp",
- "tests/external/puller_util_test.cpp",
- "tests/external/GpuStatsPuller_test.cpp",
- "tests/external/IncidentReportArgs_test.cpp",
- "tests/external/StatsPuller_test.cpp",
- "tests/indexed_priority_queue_test.cpp",
- "tests/LogEntryMatcher_test.cpp",
- "tests/LogEvent_test.cpp",
- "tests/log_event/LogEventQueue_test.cpp",
- "tests/MetricsManager_test.cpp",
- "tests/StatsLogProcessor_test.cpp",
- "tests/StatsService_test.cpp",
- "tests/UidMap_test.cpp",
- "tests/FieldValue_test.cpp",
"tests/condition/CombinationConditionTracker_test.cpp",
+ "tests/condition/ConditionTimer_test.cpp",
"tests/condition/SimpleConditionTracker_test.cpp",
"tests/condition/StateConditionTracker_test.cpp",
- "tests/condition/ConditionTimer_test.cpp",
- "tests/metrics/OringDurationTracker_test.cpp",
- "tests/metrics/MaxDurationTracker_test.cpp",
+ "tests/ConfigManager_test.cpp",
+ "tests/e2e/Alarm_e2e_test.cpp",
+ "tests/e2e/Anomaly_count_e2e_test.cpp",
+ "tests/e2e/Anomaly_duration_sum_e2e_test.cpp",
+ "tests/e2e/Attribution_e2e_test.cpp",
+ "tests/e2e/ConfigTtl_e2e_test.cpp",
+ "tests/e2e/DurationMetric_e2e_test.cpp",
+ "tests/e2e/GaugeMetric_e2e_pull_test.cpp",
+ "tests/e2e/GaugeMetric_e2e_push_test.cpp",
+ "tests/e2e/MetricActivation_e2e_test.cpp",
+ "tests/e2e/MetricConditionLink_e2e_test.cpp",
+ "tests/e2e/PartialBucket_e2e_test.cpp",
+ "tests/e2e/ValueMetric_pull_e2e_test.cpp",
+ "tests/e2e/WakelockDuration_e2e_test.cpp",
+ "tests/external/GpuStatsPuller_test.cpp",
+ "tests/external/IncidentReportArgs_test.cpp",
+ "tests/external/puller_util_test.cpp",
+ "tests/external/StatsPuller_test.cpp",
+ "tests/FieldValue_test.cpp",
+ "tests/guardrail/StatsdStats_test.cpp",
+ "tests/indexed_priority_queue_test.cpp",
+ "tests/log_event/LogEventQueue_test.cpp",
+ "tests/LogEntryMatcher_test.cpp",
+ "tests/LogEvent_test.cpp",
"tests/metrics/CountMetricProducer_test.cpp",
"tests/metrics/DurationMetricProducer_test.cpp",
"tests/metrics/EventMetricProducer_test.cpp",
- "tests/metrics/ValueMetricProducer_test.cpp",
"tests/metrics/GaugeMetricProducer_test.cpp",
- "tests/guardrail/StatsdStats_test.cpp",
+ "tests/metrics/MaxDurationTracker_test.cpp",
"tests/metrics/metrics_test_helper.cpp",
- "tests/statsd_test_util.cpp",
- "tests/storage/StorageManager_test.cpp",
- "tests/e2e/WakelockDuration_e2e_test.cpp",
- "tests/e2e/MetricActivation_e2e_test.cpp",
- "tests/e2e/MetricConditionLink_e2e_test.cpp",
- "tests/e2e/Alarm_e2e_test.cpp",
- "tests/e2e/Attribution_e2e_test.cpp",
- "tests/e2e/GaugeMetric_e2e_push_test.cpp",
- "tests/e2e/GaugeMetric_e2e_pull_test.cpp",
- "tests/e2e/ValueMetric_pull_e2e_test.cpp",
- "tests/e2e/Anomaly_count_e2e_test.cpp",
- "tests/e2e/Anomaly_duration_sum_e2e_test.cpp",
- "tests/e2e/ConfigTtl_e2e_test.cpp",
- "tests/e2e/PartialBucket_e2e_test.cpp",
- "tests/e2e/DurationMetric_e2e_test.cpp",
+ "tests/metrics/OringDurationTracker_test.cpp",
+ "tests/metrics/ValueMetricProducer_test.cpp",
+ "tests/MetricsManager_test.cpp",
"tests/shell/ShellSubscriber_test.cpp",
+ "tests/statsd_test_util.cpp",
+ "tests/StatsLogProcessor_test.cpp",
+ "tests/StatsService_test.cpp",
+ "tests/storage/StorageManager_test.cpp",
+ "tests/UidMap_test.cpp",
],
static_libs: [
@@ -287,17 +287,17 @@
// not included in libprotobuf-cpp-lite, so compile it here.
":libprotobuf-internal-protos",
+ "benchmark/duration_metric_benchmark.cpp",
+ "benchmark/filter_value_benchmark.cpp",
+ "benchmark/get_dimensions_for_condition_benchmark.cpp",
+ "benchmark/hello_world_benchmark.cpp",
+ "benchmark/log_event_benchmark.cpp",
+ "benchmark/main.cpp",
+ "benchmark/metric_util.cpp",
+ "benchmark/stats_write_benchmark.cpp",
"src/atom_field_options.proto",
"src/atoms.proto",
"src/stats_log.proto",
- "benchmark/main.cpp",
- "benchmark/hello_world_benchmark.cpp",
- "benchmark/log_event_benchmark.cpp",
- "benchmark/stats_write_benchmark.cpp",
- "benchmark/filter_value_benchmark.cpp",
- "benchmark/get_dimensions_for_condition_benchmark.cpp",
- "benchmark/metric_util.cpp",
- "benchmark/duration_metric_benchmark.cpp",
],
proto: {
@@ -337,11 +337,11 @@
},
srcs: [
- "src/stats_log.proto",
- "src/statsd_config.proto",
"src/atoms.proto",
"src/shell/shell_config.proto",
"src/shell/shell_data.proto",
+ "src/stats_log.proto",
+ "src/statsd_config.proto",
],
static_libs: [
diff --git a/cmds/statsd/src/FieldValue.cpp b/cmds/statsd/src/FieldValue.cpp
index 1185127..84a0607 100644
--- a/cmds/statsd/src/FieldValue.cpp
+++ b/cmds/statsd/src/FieldValue.cpp
@@ -116,28 +116,13 @@
}
bool isAttributionUidField(const FieldValue& value) {
- int field = value.mField.getField() & 0xff007f;
- if (field == 0x10001 && value.mValue.getType() == INT) {
- return true;
- }
- return false;
+ return isAttributionUidField(value.mField, value.mValue);
}
int32_t getUidIfExists(const FieldValue& value) {
- bool isUid = false;
// the field is uid field if the field is the uid field in attribution node or marked as
// is_uid in atoms.proto
- if (isAttributionUidField(value)) {
- isUid = true;
- } else {
- auto it = android::util::AtomsInfo::kAtomsWithUidField.find(value.mField.getTag());
- if (it != android::util::AtomsInfo::kAtomsWithUidField.end()) {
- int uidField = it->second; // uidField is the field number in proto
- isUid = value.mField.getDepth() == 0 && value.mField.getPosAtDepth(0) == uidField &&
- value.mValue.getType() == INT;
- }
- }
-
+ bool isUid = isAttributionUidField(value) || isUidField(value.mField, value.mValue);
return isUid ? value.mValue.int_value : -1;
}
@@ -153,7 +138,7 @@
auto it = android::util::AtomsInfo::kAtomsWithUidField.find(field.getTag());
if (it != android::util::AtomsInfo::kAtomsWithUidField.end()) {
- int uidField = it->second;
+ int uidField = it->second; // uidField is the field number in proto
return field.getDepth() == 0 && field.getPosAtDepth(0) == uidField &&
value.getType() == INT;
}
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index b50ec8a..f8a3ede 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -335,6 +335,8 @@
UpdateEngineUpdateAttemptReported update_engine_update_attempt_reported = 225;
UpdateEngineSuccessfulUpdateReported update_engine_successful_update_reported = 226;
CameraActionEvent camera_action_event = 227;
+ AppCompatibilityChangeReported app_compatibility_change_reported =
+ 228 [(allow_from_any_uid) = true];
}
// Pulled events will start at field 10000.
@@ -7170,7 +7172,6 @@
repeated int64 frame_counts = 2;
}
-
/**
* Information about camera facing and API level usage.
* Logged from:
@@ -7195,3 +7196,39 @@
}
optional Facing facing = 4;
}
+
+/**
+ * Logs when a compatibility change is affecting an app.
+ *
+ * Logged from:
+ * frameworks/base/core/java/android/app/AppCompatCallbacks.java and
+ * frameworks/base/services/core/java/com/android/server/compat/PlatformCompat.java
+ */
+message AppCompatibilityChangeReported {
+ // The UID of the app being affected by the compatibilty change.
+ optional int32 uid = 1 [(is_uid) = true];
+
+ // The ID of the change affecting the app.
+ optional int64 change_id = 2;
+
+ enum State {
+ UNKNOWN_STATE = 0;
+ ENABLED = 1;
+ DISABLED = 2;
+ LOGGED = 3;
+ }
+
+ // The state of the change - if logged from gating whether it was enabled or disabled, or just
+ // logged otherwise.
+ optional State state = 3;
+
+ enum Source {
+ UNKNOWN_SOURCE = 0;
+ APP_PROCESS = 1;
+ SYSTEM_SERVER = 2;
+ }
+
+ // Where it was logged from.
+ optional Source source = 4;
+
+}
diff --git a/cmds/statsd/src/matchers/matcher_util.cpp b/cmds/statsd/src/matchers/matcher_util.cpp
index 10ac4a1..476fae3 100644
--- a/cmds/statsd/src/matchers/matcher_util.cpp
+++ b/cmds/statsd/src/matchers/matcher_util.cpp
@@ -358,9 +358,10 @@
bool matchesSimple(const UidMap& uidMap, const SimpleAtomMatcher& simpleMatcher,
const LogEvent& event) {
- if (simpleMatcher.field_value_matcher_size() <= 0) {
- return event.GetTagId() == simpleMatcher.atom_id();
+ if (event.GetTagId() != simpleMatcher.atom_id()) {
+ return false;
}
+
for (const auto& matcher : simpleMatcher.field_value_matcher()) {
if (!matchesSimple(uidMap, matcher, event.getValues(), 0, event.getValues().size(), 0)) {
return false;
diff --git a/cmds/statsd/tests/LogEntryMatcher_test.cpp b/cmds/statsd/tests/LogEntryMatcher_test.cpp
index 70f0f6f..441d3c8 100644
--- a/cmds/statsd/tests/LogEntryMatcher_test.cpp
+++ b/cmds/statsd/tests/LogEntryMatcher_test.cpp
@@ -330,6 +330,7 @@
EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
// Tag found in kAtomsWithUidField and has matching uid
+ simpleMatcher->set_atom_id(TAG_ID_2);
EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2));
// Tag found in kAtomsWithUidField but has non-matching uid
diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt
index b4913c8..a6e1f0a 100644
--- a/config/hiddenapi-greylist.txt
+++ b/config/hiddenapi-greylist.txt
@@ -1180,4 +1180,283 @@
Lcom/android/server/ResettableTimeout$T;-><init>(Lcom/android/server/ResettableTimeout;)V
Lcom/google/android/gles_jni/EGLImpl;-><init>()V
Lcom/google/android/gles_jni/GLImpl;-><init>()V
+Lcom/google/android/mms/ContentType;->getAudioTypes()Ljava/util/ArrayList;
+Lcom/google/android/mms/ContentType;->getImageTypes()Ljava/util/ArrayList;
+Lcom/google/android/mms/ContentType;->getVideoTypes()Ljava/util/ArrayList;
+Lcom/google/android/mms/ContentType;->isAudioType(Ljava/lang/String;)Z
+Lcom/google/android/mms/ContentType;->isDrmType(Ljava/lang/String;)Z
+Lcom/google/android/mms/ContentType;->isImageType(Ljava/lang/String;)Z
+Lcom/google/android/mms/ContentType;->isSupportedAudioType(Ljava/lang/String;)Z
+Lcom/google/android/mms/ContentType;->isSupportedImageType(Ljava/lang/String;)Z
+Lcom/google/android/mms/ContentType;->isSupportedType(Ljava/lang/String;)Z
+Lcom/google/android/mms/ContentType;->isSupportedVideoType(Ljava/lang/String;)Z
+Lcom/google/android/mms/ContentType;->isTextType(Ljava/lang/String;)Z
+Lcom/google/android/mms/ContentType;->isVideoType(Ljava/lang/String;)Z
+Lcom/google/android/mms/InvalidHeaderValueException;-><init>(Ljava/lang/String;)V
+Lcom/google/android/mms/MmsException;-><init>()V
+Lcom/google/android/mms/MmsException;-><init>(Ljava/lang/String;)V
+Lcom/google/android/mms/MmsException;-><init>(Ljava/lang/String;Ljava/lang/Throwable;)V
+Lcom/google/android/mms/MmsException;-><init>(Ljava/lang/Throwable;)V
+Lcom/google/android/mms/pdu/AcknowledgeInd;-><init>(I[B)V
+Lcom/google/android/mms/pdu/AcknowledgeInd;-><init>(Lcom/google/android/mms/pdu/PduHeaders;)V
+Lcom/google/android/mms/pdu/AcknowledgeInd;->setReportAllowed(I)V
+Lcom/google/android/mms/pdu/AcknowledgeInd;->setTransactionId([B)V
+Lcom/google/android/mms/pdu/Base64;->decodeBase64([B)[B
+Lcom/google/android/mms/pdu/CharacterSets;->getMibEnumValue(Ljava/lang/String;)I
+Lcom/google/android/mms/pdu/CharacterSets;->getMimeName(I)Ljava/lang/String;
+Lcom/google/android/mms/pdu/DeliveryInd;-><init>(Lcom/google/android/mms/pdu/PduHeaders;)V
+Lcom/google/android/mms/pdu/DeliveryInd;->getDate()J
+Lcom/google/android/mms/pdu/DeliveryInd;->getMessageId()[B
+Lcom/google/android/mms/pdu/DeliveryInd;->getStatus()I
+Lcom/google/android/mms/pdu/DeliveryInd;->getTo()[Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/EncodedStringValue;-><init>(I[B)V
+Lcom/google/android/mms/pdu/EncodedStringValue;-><init>(Ljava/lang/String;)V
+Lcom/google/android/mms/pdu/EncodedStringValue;-><init>([B)V
+Lcom/google/android/mms/pdu/EncodedStringValue;->appendTextString([B)V
+Lcom/google/android/mms/pdu/EncodedStringValue;->concat([Lcom/google/android/mms/pdu/EncodedStringValue;)Ljava/lang/String;
+Lcom/google/android/mms/pdu/EncodedStringValue;->copy(Lcom/google/android/mms/pdu/EncodedStringValue;)Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/EncodedStringValue;->encodeStrings([Ljava/lang/String;)[Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/EncodedStringValue;->extract(Ljava/lang/String;)[Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/EncodedStringValue;->getCharacterSet()I
+Lcom/google/android/mms/pdu/EncodedStringValue;->getString()Ljava/lang/String;
+Lcom/google/android/mms/pdu/EncodedStringValue;->getTextString()[B
+Lcom/google/android/mms/pdu/EncodedStringValue;->setCharacterSet(I)V
+Lcom/google/android/mms/pdu/EncodedStringValue;->setTextString([B)V
+Lcom/google/android/mms/pdu/GenericPdu;-><init>()V
+Lcom/google/android/mms/pdu/GenericPdu;->getFrom()Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/GenericPdu;->getMessageType()I
+Lcom/google/android/mms/pdu/GenericPdu;->getPduHeaders()Lcom/google/android/mms/pdu/PduHeaders;
+Lcom/google/android/mms/pdu/GenericPdu;->mPduHeaders:Lcom/google/android/mms/pdu/PduHeaders;
+Lcom/google/android/mms/pdu/GenericPdu;->setFrom(Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/GenericPdu;->setMessageType(I)V
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;-><init>()V
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;-><init>(Lcom/google/android/mms/pdu/PduHeaders;Lcom/google/android/mms/pdu/PduBody;)V
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;->addTo(Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;->getBody()Lcom/google/android/mms/pdu/PduBody;
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;->getDate()J
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;->getPriority()I
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;->getSubject()Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;->getTo()[Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;->setBody(Lcom/google/android/mms/pdu/PduBody;)V
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;->setDate(J)V
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;->setPriority(I)V
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;->setSubject(Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/NotificationInd;-><init>()V
+Lcom/google/android/mms/pdu/NotificationInd;-><init>(Lcom/google/android/mms/pdu/PduHeaders;)V
+Lcom/google/android/mms/pdu/NotificationInd;->getContentClass()I
+Lcom/google/android/mms/pdu/NotificationInd;->getContentLocation()[B
+Lcom/google/android/mms/pdu/NotificationInd;->getDeliveryReport()I
+Lcom/google/android/mms/pdu/NotificationInd;->getExpiry()J
+Lcom/google/android/mms/pdu/NotificationInd;->getFrom()Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/NotificationInd;->getMessageClass()[B
+Lcom/google/android/mms/pdu/NotificationInd;->getMessageSize()J
+Lcom/google/android/mms/pdu/NotificationInd;->getSubject()Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/NotificationInd;->getTransactionId()[B
+Lcom/google/android/mms/pdu/NotificationInd;->setContentClass(I)V
+Lcom/google/android/mms/pdu/NotificationInd;->setContentLocation([B)V
+Lcom/google/android/mms/pdu/NotificationInd;->setDeliveryReport(I)V
+Lcom/google/android/mms/pdu/NotificationInd;->setExpiry(J)V
+Lcom/google/android/mms/pdu/NotificationInd;->setFrom(Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/NotificationInd;->setMessageClass([B)V
+Lcom/google/android/mms/pdu/NotificationInd;->setMessageSize(J)V
+Lcom/google/android/mms/pdu/NotificationInd;->setSubject(Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/NotificationInd;->setTransactionId([B)V
+Lcom/google/android/mms/pdu/NotifyRespInd;-><init>(I[BI)V
+Lcom/google/android/mms/pdu/NotifyRespInd;-><init>(Lcom/google/android/mms/pdu/PduHeaders;)V
+Lcom/google/android/mms/pdu/NotifyRespInd;->setReportAllowed(I)V
+Lcom/google/android/mms/pdu/NotifyRespInd;->setStatus(I)V
+Lcom/google/android/mms/pdu/NotifyRespInd;->setTransactionId([B)V
+Lcom/google/android/mms/pdu/PduBody;-><init>()V
+Lcom/google/android/mms/pdu/PduBody;->addPart(ILcom/google/android/mms/pdu/PduPart;)V
+Lcom/google/android/mms/pdu/PduBody;->addPart(Lcom/google/android/mms/pdu/PduPart;)Z
+Lcom/google/android/mms/pdu/PduBody;->getPart(I)Lcom/google/android/mms/pdu/PduPart;
+Lcom/google/android/mms/pdu/PduBody;->getPartByContentId(Ljava/lang/String;)Lcom/google/android/mms/pdu/PduPart;
+Lcom/google/android/mms/pdu/PduBody;->getPartByContentLocation(Ljava/lang/String;)Lcom/google/android/mms/pdu/PduPart;
+Lcom/google/android/mms/pdu/PduBody;->getPartByFileName(Ljava/lang/String;)Lcom/google/android/mms/pdu/PduPart;
+Lcom/google/android/mms/pdu/PduBody;->getPartByName(Ljava/lang/String;)Lcom/google/android/mms/pdu/PduPart;
+Lcom/google/android/mms/pdu/PduBody;->getPartIndex(Lcom/google/android/mms/pdu/PduPart;)I
+Lcom/google/android/mms/pdu/PduBody;->getPartsNum()I
+Lcom/google/android/mms/pdu/PduBody;->removePart(I)Lcom/google/android/mms/pdu/PduPart;
+Lcom/google/android/mms/pdu/PduComposer$BufferStack;->copy()V
+Lcom/google/android/mms/pdu/PduComposer$BufferStack;->mark()Lcom/google/android/mms/pdu/PduComposer$PositionMarker;
+Lcom/google/android/mms/pdu/PduComposer$BufferStack;->newbuf()V
+Lcom/google/android/mms/pdu/PduComposer$BufferStack;->pop()V
+Lcom/google/android/mms/pdu/PduComposer$PositionMarker;->getLength()I
+Lcom/google/android/mms/pdu/PduComposer;-><init>(Landroid/content/Context;Lcom/google/android/mms/pdu/GenericPdu;)V
+Lcom/google/android/mms/pdu/PduComposer;->appendEncodedString(Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/PduComposer;->appendHeader(I)I
+Lcom/google/android/mms/pdu/PduComposer;->appendLongInteger(J)V
+Lcom/google/android/mms/pdu/PduComposer;->appendOctet(I)V
+Lcom/google/android/mms/pdu/PduComposer;->appendQuotedString(Ljava/lang/String;)V
+Lcom/google/android/mms/pdu/PduComposer;->appendQuotedString([B)V
+Lcom/google/android/mms/pdu/PduComposer;->appendShortInteger(I)V
+Lcom/google/android/mms/pdu/PduComposer;->appendTextString(Ljava/lang/String;)V
+Lcom/google/android/mms/pdu/PduComposer;->appendTextString([B)V
+Lcom/google/android/mms/pdu/PduComposer;->appendUintvarInteger(J)V
+Lcom/google/android/mms/pdu/PduComposer;->appendValueLength(J)V
+Lcom/google/android/mms/pdu/PduComposer;->arraycopy([BII)V
+Lcom/google/android/mms/pdu/PduComposer;->make()[B
+Lcom/google/android/mms/pdu/PduComposer;->mContentTypeMap:Ljava/util/HashMap;
+Lcom/google/android/mms/pdu/PduComposer;->mMessage:Ljava/io/ByteArrayOutputStream;
+Lcom/google/android/mms/pdu/PduComposer;->mPdu:Lcom/google/android/mms/pdu/GenericPdu;
+Lcom/google/android/mms/pdu/PduComposer;->mPduHeader:Lcom/google/android/mms/pdu/PduHeaders;
+Lcom/google/android/mms/pdu/PduComposer;->mPosition:I
+Lcom/google/android/mms/pdu/PduComposer;->mResolver:Landroid/content/ContentResolver;
+Lcom/google/android/mms/pdu/PduComposer;->mStack:Lcom/google/android/mms/pdu/PduComposer$BufferStack;
+Lcom/google/android/mms/pdu/PduContentTypes;->contentTypes:[Ljava/lang/String;
+Lcom/google/android/mms/pdu/PduHeaders;-><init>()V
+Lcom/google/android/mms/pdu/PduHeaders;->appendEncodedStringValue(Lcom/google/android/mms/pdu/EncodedStringValue;I)V
+Lcom/google/android/mms/pdu/PduHeaders;->getEncodedStringValue(I)Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/PduHeaders;->getEncodedStringValues(I)[Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/PduHeaders;->getLongInteger(I)J
+Lcom/google/android/mms/pdu/PduHeaders;->getOctet(I)I
+Lcom/google/android/mms/pdu/PduHeaders;->getTextString(I)[B
+Lcom/google/android/mms/pdu/PduHeaders;->setEncodedStringValue(Lcom/google/android/mms/pdu/EncodedStringValue;I)V
+Lcom/google/android/mms/pdu/PduHeaders;->setLongInteger(JI)V
+Lcom/google/android/mms/pdu/PduHeaders;->setOctet(II)V
+Lcom/google/android/mms/pdu/PduParser;-><init>([BZ)V
+Lcom/google/android/mms/pdu/PduParser;->checkPartPosition(Lcom/google/android/mms/pdu/PduPart;)I
+Lcom/google/android/mms/pdu/PduParser;->log(Ljava/lang/String;)V
+Lcom/google/android/mms/pdu/PduParser;->parse()Lcom/google/android/mms/pdu/GenericPdu;
+Lcom/google/android/mms/pdu/PduParser;->parseContentType(Ljava/io/ByteArrayInputStream;Ljava/util/HashMap;)[B
+Lcom/google/android/mms/pdu/PduParser;->parsePartHeaders(Ljava/io/ByteArrayInputStream;Lcom/google/android/mms/pdu/PduPart;I)Z
+Lcom/google/android/mms/pdu/PduParser;->parseShortInteger(Ljava/io/ByteArrayInputStream;)I
+Lcom/google/android/mms/pdu/PduParser;->parseUnsignedInt(Ljava/io/ByteArrayInputStream;)I
+Lcom/google/android/mms/pdu/PduParser;->parseValueLength(Ljava/io/ByteArrayInputStream;)I
+Lcom/google/android/mms/pdu/PduParser;->parseWapString(Ljava/io/ByteArrayInputStream;I)[B
+Lcom/google/android/mms/pdu/PduPart;-><init>()V
+Lcom/google/android/mms/pdu/PduPart;->generateLocation()Ljava/lang/String;
+Lcom/google/android/mms/pdu/PduPart;->getCharset()I
+Lcom/google/android/mms/pdu/PduPart;->getContentDisposition()[B
+Lcom/google/android/mms/pdu/PduPart;->getContentId()[B
+Lcom/google/android/mms/pdu/PduPart;->getContentLocation()[B
+Lcom/google/android/mms/pdu/PduPart;->getContentTransferEncoding()[B
+Lcom/google/android/mms/pdu/PduPart;->getContentType()[B
+Lcom/google/android/mms/pdu/PduPart;->getData()[B
+Lcom/google/android/mms/pdu/PduPart;->getDataLength()I
+Lcom/google/android/mms/pdu/PduPart;->getDataUri()Landroid/net/Uri;
+Lcom/google/android/mms/pdu/PduPart;->getFilename()[B
+Lcom/google/android/mms/pdu/PduPart;->getName()[B
+Lcom/google/android/mms/pdu/PduPart;->setCharset(I)V
+Lcom/google/android/mms/pdu/PduPart;->setContentDisposition([B)V
+Lcom/google/android/mms/pdu/PduPart;->setContentId([B)V
+Lcom/google/android/mms/pdu/PduPart;->setContentLocation([B)V
+Lcom/google/android/mms/pdu/PduPart;->setContentTransferEncoding([B)V
+Lcom/google/android/mms/pdu/PduPart;->setContentType([B)V
+Lcom/google/android/mms/pdu/PduPart;->setData([B)V
+Lcom/google/android/mms/pdu/PduPart;->setDataUri(Landroid/net/Uri;)V
+Lcom/google/android/mms/pdu/PduPart;->setFilename([B)V
+Lcom/google/android/mms/pdu/PduPart;->setName([B)V
+Lcom/google/android/mms/pdu/PduPersister;->ADDRESS_FIELDS:[I
+Lcom/google/android/mms/pdu/PduPersister;->CHARSET_COLUMN_NAME_MAP:Ljava/util/HashMap;
+Lcom/google/android/mms/pdu/PduPersister;->ENCODED_STRING_COLUMN_NAME_MAP:Ljava/util/HashMap;
+Lcom/google/android/mms/pdu/PduPersister;->getByteArrayFromPartColumn(Landroid/database/Cursor;I)[B
+Lcom/google/android/mms/pdu/PduPersister;->getBytes(Ljava/lang/String;)[B
+Lcom/google/android/mms/pdu/PduPersister;->getIntegerFromPartColumn(Landroid/database/Cursor;I)Ljava/lang/Integer;
+Lcom/google/android/mms/pdu/PduPersister;->getPartContentType(Lcom/google/android/mms/pdu/PduPart;)Ljava/lang/String;
+Lcom/google/android/mms/pdu/PduPersister;->getPduPersister(Landroid/content/Context;)Lcom/google/android/mms/pdu/PduPersister;
+Lcom/google/android/mms/pdu/PduPersister;->getPendingMessages(J)Landroid/database/Cursor;
+Lcom/google/android/mms/pdu/PduPersister;->load(Landroid/net/Uri;)Lcom/google/android/mms/pdu/GenericPdu;
+Lcom/google/android/mms/pdu/PduPersister;->loadRecipients(ILjava/util/HashSet;Ljava/util/HashMap;Z)V
+Lcom/google/android/mms/pdu/PduPersister;->LONG_COLUMN_NAME_MAP:Ljava/util/HashMap;
+Lcom/google/android/mms/pdu/PduPersister;->mContentResolver:Landroid/content/ContentResolver;
+Lcom/google/android/mms/pdu/PduPersister;->mContext:Landroid/content/Context;
+Lcom/google/android/mms/pdu/PduPersister;->MESSAGE_BOX_MAP:Ljava/util/HashMap;
+Lcom/google/android/mms/pdu/PduPersister;->move(Landroid/net/Uri;Landroid/net/Uri;)Landroid/net/Uri;
+Lcom/google/android/mms/pdu/PduPersister;->mTelephonyManager:Landroid/telephony/TelephonyManager;
+Lcom/google/android/mms/pdu/PduPersister;->OCTET_COLUMN_NAME_MAP:Ljava/util/HashMap;
+Lcom/google/android/mms/pdu/PduPersister;->PART_PROJECTION:[Ljava/lang/String;
+Lcom/google/android/mms/pdu/PduPersister;->PDU_CACHE_INSTANCE:Lcom/google/android/mms/util/PduCache;
+Lcom/google/android/mms/pdu/PduPersister;->persist(Lcom/google/android/mms/pdu/GenericPdu;Landroid/net/Uri;ZZLjava/util/HashMap;)Landroid/net/Uri;
+Lcom/google/android/mms/pdu/PduPersister;->persistAddress(JI[Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/PduPersister;->persistPart(Lcom/google/android/mms/pdu/PduPart;JLjava/util/HashMap;)Landroid/net/Uri;
+Lcom/google/android/mms/pdu/PduPersister;->TEXT_STRING_COLUMN_NAME_MAP:Ljava/util/HashMap;
+Lcom/google/android/mms/pdu/PduPersister;->toIsoString([B)Ljava/lang/String;
+Lcom/google/android/mms/pdu/PduPersister;->updateAddress(JI[Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/PduPersister;->updateHeaders(Landroid/net/Uri;Lcom/google/android/mms/pdu/SendReq;)V
+Lcom/google/android/mms/pdu/PduPersister;->updateParts(Landroid/net/Uri;Lcom/google/android/mms/pdu/PduBody;Ljava/util/HashMap;)V
+Lcom/google/android/mms/pdu/QuotedPrintable;->decodeQuotedPrintable([B)[B
+Lcom/google/android/mms/pdu/ReadOrigInd;-><init>(Lcom/google/android/mms/pdu/PduHeaders;)V
+Lcom/google/android/mms/pdu/ReadOrigInd;->getMessageId()[B
+Lcom/google/android/mms/pdu/ReadOrigInd;->getReadStatus()I
+Lcom/google/android/mms/pdu/ReadRecInd;-><init>(Lcom/google/android/mms/pdu/EncodedStringValue;[BII[Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/ReadRecInd;-><init>(Lcom/google/android/mms/pdu/PduHeaders;)V
+Lcom/google/android/mms/pdu/ReadRecInd;->getMessageId()[B
+Lcom/google/android/mms/pdu/ReadRecInd;->setDate(J)V
+Lcom/google/android/mms/pdu/RetrieveConf;-><init>()V
+Lcom/google/android/mms/pdu/RetrieveConf;-><init>(Lcom/google/android/mms/pdu/PduHeaders;Lcom/google/android/mms/pdu/PduBody;)V
+Lcom/google/android/mms/pdu/RetrieveConf;->addCc(Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/RetrieveConf;->getCc()[Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/RetrieveConf;->getContentType()[B
+Lcom/google/android/mms/pdu/RetrieveConf;->getDeliveryReport()I
+Lcom/google/android/mms/pdu/RetrieveConf;->getFrom()Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/RetrieveConf;->getMessageClass()[B
+Lcom/google/android/mms/pdu/RetrieveConf;->getMessageId()[B
+Lcom/google/android/mms/pdu/RetrieveConf;->getReadReport()I
+Lcom/google/android/mms/pdu/RetrieveConf;->getRetrieveStatus()I
+Lcom/google/android/mms/pdu/RetrieveConf;->getRetrieveText()Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/RetrieveConf;->getTransactionId()[B
+Lcom/google/android/mms/pdu/RetrieveConf;->setContentType([B)V
+Lcom/google/android/mms/pdu/RetrieveConf;->setDeliveryReport(I)V
+Lcom/google/android/mms/pdu/RetrieveConf;->setFrom(Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/RetrieveConf;->setMessageClass([B)V
+Lcom/google/android/mms/pdu/RetrieveConf;->setMessageId([B)V
+Lcom/google/android/mms/pdu/RetrieveConf;->setReadReport(I)V
+Lcom/google/android/mms/pdu/RetrieveConf;->setRetrieveStatus(I)V
+Lcom/google/android/mms/pdu/RetrieveConf;->setRetrieveText(Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/RetrieveConf;->setTransactionId([B)V
+Lcom/google/android/mms/pdu/SendConf;-><init>()V
+Lcom/google/android/mms/pdu/SendConf;-><init>(Lcom/google/android/mms/pdu/PduHeaders;)V
+Lcom/google/android/mms/pdu/SendConf;->getMessageId()[B
+Lcom/google/android/mms/pdu/SendConf;->getResponseStatus()I
+Lcom/google/android/mms/pdu/SendConf;->getTransactionId()[B
+Lcom/google/android/mms/pdu/SendReq;-><init>()V
+Lcom/google/android/mms/pdu/SendReq;-><init>(Lcom/google/android/mms/pdu/PduHeaders;Lcom/google/android/mms/pdu/PduBody;)V
+Lcom/google/android/mms/pdu/SendReq;->addBcc(Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/SendReq;->addCc(Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/SendReq;->getBcc()[Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/SendReq;->getCc()[Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/SendReq;->getContentType()[B
+Lcom/google/android/mms/pdu/SendReq;->getDeliveryReport()I
+Lcom/google/android/mms/pdu/SendReq;->getExpiry()J
+Lcom/google/android/mms/pdu/SendReq;->getMessageClass()[B
+Lcom/google/android/mms/pdu/SendReq;->getMessageSize()J
+Lcom/google/android/mms/pdu/SendReq;->getReadReport()I
+Lcom/google/android/mms/pdu/SendReq;->getTransactionId()[B
+Lcom/google/android/mms/pdu/SendReq;->setBcc([Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/SendReq;->setCc([Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/SendReq;->setContentType([B)V
+Lcom/google/android/mms/pdu/SendReq;->setDeliveryReport(I)V
+Lcom/google/android/mms/pdu/SendReq;->setExpiry(J)V
+Lcom/google/android/mms/pdu/SendReq;->setMessageClass([B)V
+Lcom/google/android/mms/pdu/SendReq;->setMessageSize(J)V
+Lcom/google/android/mms/pdu/SendReq;->setReadReport(I)V
+Lcom/google/android/mms/pdu/SendReq;->setTo([Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/SendReq;->setTransactionId([B)V
+Lcom/google/android/mms/util/AbstractCache;-><init>()V
+Lcom/google/android/mms/util/AbstractCache;->get(Ljava/lang/Object;)Ljava/lang/Object;
+Lcom/google/android/mms/util/AbstractCache;->purge(Ljava/lang/Object;)Ljava/lang/Object;
+Lcom/google/android/mms/util/AbstractCache;->purgeAll()V
+Lcom/google/android/mms/util/AbstractCache;->put(Ljava/lang/Object;Ljava/lang/Object;)Z
+Lcom/google/android/mms/util/DownloadDrmHelper;->isDrmConvertNeeded(Ljava/lang/String;)Z
+Lcom/google/android/mms/util/DownloadDrmHelper;->modifyDrmFwLockFileExtension(Ljava/lang/String;)Ljava/lang/String;
+Lcom/google/android/mms/util/DrmConvertSession;->close(Ljava/lang/String;)I
+Lcom/google/android/mms/util/DrmConvertSession;->convert([BI)[B
+Lcom/google/android/mms/util/DrmConvertSession;->open(Landroid/content/Context;Ljava/lang/String;)Lcom/google/android/mms/util/DrmConvertSession;
+Lcom/google/android/mms/util/PduCache;-><init>()V
+Lcom/google/android/mms/util/PduCache;->getInstance()Lcom/google/android/mms/util/PduCache;
+Lcom/google/android/mms/util/PduCache;->isUpdating(Landroid/net/Uri;)Z
+Lcom/google/android/mms/util/PduCache;->purge(Landroid/net/Uri;)Lcom/google/android/mms/util/PduCacheEntry;
+Lcom/google/android/mms/util/PduCache;->purge(Ljava/lang/Object;)Ljava/lang/Object;
+Lcom/google/android/mms/util/PduCache;->purgeAll()V
+Lcom/google/android/mms/util/PduCacheEntry;-><init>(Lcom/google/android/mms/pdu/GenericPdu;IJ)V
+Lcom/google/android/mms/util/PduCacheEntry;->getMessageBox()I
+Lcom/google/android/mms/util/PduCacheEntry;->getPdu()Lcom/google/android/mms/pdu/GenericPdu;
+Lcom/google/android/mms/util/PduCacheEntry;->getThreadId()J
+Lcom/google/android/mms/util/SqliteWrapper;->checkSQLiteException(Landroid/content/Context;Landroid/database/sqlite/SQLiteException;)V
+Lcom/google/android/mms/util/SqliteWrapper;->delete(Landroid/content/Context;Landroid/content/ContentResolver;Landroid/net/Uri;Ljava/lang/String;[Ljava/lang/String;)I
+Lcom/google/android/mms/util/SqliteWrapper;->insert(Landroid/content/Context;Landroid/content/ContentResolver;Landroid/net/Uri;Landroid/content/ContentValues;)Landroid/net/Uri;
+Lcom/google/android/mms/util/SqliteWrapper;->query(Landroid/content/Context;Landroid/content/ContentResolver;Landroid/net/Uri;[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor;
+Lcom/google/android/mms/util/SqliteWrapper;->requery(Landroid/content/Context;Landroid/database/Cursor;)Z
+Lcom/google/android/mms/util/SqliteWrapper;->update(Landroid/content/Context;Landroid/content/ContentResolver;Landroid/net/Uri;Landroid/content/ContentValues;Ljava/lang/String;[Ljava/lang/String;)I
Lcom/google/android/util/AbstractMessageParser$Token$Type;->values()[Lcom/google/android/util/AbstractMessageParser$Token$Type;
diff --git a/core/java/android/accessibilityservice/AccessibilityGestureInfo.aidl b/core/java/android/accessibilityservice/AccessibilityGestureEvent.aidl
similarity index 94%
rename from core/java/android/accessibilityservice/AccessibilityGestureInfo.aidl
rename to core/java/android/accessibilityservice/AccessibilityGestureEvent.aidl
index 2539051..58a9b64 100644
--- a/core/java/android/accessibilityservice/AccessibilityGestureInfo.aidl
+++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.aidl
@@ -16,4 +16,4 @@
package android.accessibilityservice;
-parcelable AccessibilityGestureInfo;
+parcelable AccessibilityGestureEvent;
diff --git a/core/java/android/accessibilityservice/AccessibilityGestureInfo.java b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
similarity index 86%
rename from core/java/android/accessibilityservice/AccessibilityGestureInfo.java
rename to core/java/android/accessibilityservice/AccessibilityGestureEvent.java
index 28c1dea..b540175 100644
--- a/core/java/android/accessibilityservice/AccessibilityGestureInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
@@ -44,17 +44,17 @@
import java.lang.annotation.RetentionPolicy;
/**
- * This class describes the gesture information including gesture id and which display it happens
+ * This class describes the gesture event including gesture id and which display it happens
* on.
* <p>
* <strong>Note:</strong> Accessibility services setting the
* {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE}
* flag can receive gestures.
*
- * @see AccessibilityService#onGesture(AccessibilityGestureInfo)
+ * @see AccessibilityService#onGesture(AccessibilityGestureEvent)
*/
-public final class AccessibilityGestureInfo implements Parcelable {
+public final class AccessibilityGestureEvent implements Parcelable {
/** @hide */
@IntDef(prefix = { "GESTURE_" }, value = {
@@ -84,12 +84,12 @@
/** @hide */
@TestApi
- public AccessibilityGestureInfo(int gestureId, int displayId) {
+ public AccessibilityGestureEvent(int gestureId, int displayId) {
mGestureId = gestureId;
mDisplayId = displayId;
}
- private AccessibilityGestureInfo(@NonNull Parcel parcel) {
+ private AccessibilityGestureEvent(@NonNull Parcel parcel) {
mGestureId = parcel.readInt();
mDisplayId = parcel.readInt();
}
@@ -117,7 +117,7 @@
@NonNull
@Override
public String toString() {
- StringBuilder stringBuilder = new StringBuilder("AccessibilityGestureInfo[");
+ StringBuilder stringBuilder = new StringBuilder("AccessibilityGestureEvent[");
stringBuilder.append("gestureId: ").append(mGestureId);
stringBuilder.append(", ");
stringBuilder.append("displayId: ").append(mDisplayId);
@@ -142,14 +142,14 @@
/**
* @see Parcelable.Creator
*/
- public static final @NonNull Parcelable.Creator<AccessibilityGestureInfo> CREATOR =
- new Parcelable.Creator<AccessibilityGestureInfo>() {
- public AccessibilityGestureInfo createFromParcel(Parcel parcel) {
- return new AccessibilityGestureInfo(parcel);
+ public static final @NonNull Parcelable.Creator<AccessibilityGestureEvent> CREATOR =
+ new Parcelable.Creator<AccessibilityGestureEvent>() {
+ public AccessibilityGestureEvent createFromParcel(Parcel parcel) {
+ return new AccessibilityGestureEvent(parcel);
}
- public AccessibilityGestureInfo[] newArray(int size) {
- return new AccessibilityGestureInfo[size];
+ public AccessibilityGestureEvent[] newArray(int size) {
+ return new AccessibilityGestureEvent[size];
}
};
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index a8daf91..d3c274f 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -382,7 +382,7 @@
void onServiceConnected();
void init(int connectionId, IBinder windowToken);
/** The detected gesture information for different displays */
- boolean onGesture(AccessibilityGestureInfo gestureInfo);
+ boolean onGesture(AccessibilityGestureEvent gestureInfo);
boolean onKeyEvent(KeyEvent event);
/** Magnification changed callbacks for different displays */
void onMagnificationChanged(int displayId, @NonNull Region region,
@@ -517,7 +517,7 @@
}
/**
- * Called by {@link #onGesture(AccessibilityGestureInfo)} when the user performs a specific
+ * Called by {@link #onGesture(AccessibilityGestureEvent)} when the user performs a specific
* gesture on the default display.
*
* <strong>Note:</strong> To receive gestures an accessibility service must
@@ -528,7 +528,7 @@
* @param gestureId The unique id of the performed gesture.
*
* @return Whether the gesture was handled.
- * @deprecated Override {@link #onGesture(AccessibilityGestureInfo)} instead.
+ * @deprecated Override {@link #onGesture(AccessibilityGestureEvent)} instead.
*
* @see #GESTURE_SWIPE_UP
* @see #GESTURE_SWIPE_UP_AND_LEFT
@@ -564,14 +564,14 @@
* <strong>Note:</strong> The default implementation calls {@link #onGesture(int)} when the
* touch screen is default display.
*
- * @param gestureInfo The information of gesture.
+ * @param gestureEvent The information of gesture.
*
* @return Whether the gesture was handled.
*
*/
- public boolean onGesture(@NonNull AccessibilityGestureInfo gestureInfo) {
- if (gestureInfo.getDisplayId() == Display.DEFAULT_DISPLAY) {
- onGesture(gestureInfo.getGestureId());
+ public boolean onGesture(@NonNull AccessibilityGestureEvent gestureEvent) {
+ if (gestureEvent.getDisplayId() == Display.DEFAULT_DISPLAY) {
+ onGesture(gestureEvent.getGestureId());
}
return false;
}
@@ -1725,8 +1725,8 @@
}
@Override
- public boolean onGesture(AccessibilityGestureInfo gestureInfo) {
- return AccessibilityService.this.onGesture(gestureInfo);
+ public boolean onGesture(AccessibilityGestureEvent gestureEvent) {
+ return AccessibilityService.this.onGesture(gestureEvent);
}
@Override
@@ -1826,7 +1826,7 @@
}
@Override
- public void onGesture(AccessibilityGestureInfo gestureInfo) {
+ public void onGesture(AccessibilityGestureEvent gestureInfo) {
Message message = mCaller.obtainMessageO(DO_ON_GESTURE, gestureInfo);
mCaller.sendMessage(message);
}
@@ -1942,7 +1942,7 @@
case DO_ON_GESTURE: {
if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
- mCallback.onGesture((AccessibilityGestureInfo) message.obj);
+ mCallback.onGesture((AccessibilityGestureEvent) message.obj);
}
} return;
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
index e0d5e44..8ec3aea 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
@@ -20,7 +20,7 @@
import android.graphics.Region;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityWindowInfo;
-import android.accessibilityservice.AccessibilityGestureInfo;
+import android.accessibilityservice.AccessibilityGestureEvent;
import android.view.KeyEvent;
/**
@@ -36,7 +36,7 @@
void onInterrupt();
- void onGesture(in AccessibilityGestureInfo gestureInfo);
+ void onGesture(in AccessibilityGestureEvent gestureEvent);
void clearAccessibilityCache();
diff --git a/core/java/android/app/AppCompatCallbacks.java b/core/java/android/app/AppCompatCallbacks.java
index 17697db..08c97eb 100644
--- a/core/java/android/app/AppCompatCallbacks.java
+++ b/core/java/android/app/AppCompatCallbacks.java
@@ -19,6 +19,9 @@
import android.compat.Compatibility;
import android.os.Process;
import android.util.Log;
+import android.util.StatsLog;
+
+import com.android.internal.compat.ChangeReporter;
import java.util.Arrays;
@@ -28,10 +31,10 @@
* @hide
*/
public final class AppCompatCallbacks extends Compatibility.Callbacks {
-
private static final String TAG = "Compatibility";
private final long[] mDisabledChanges;
+ private final ChangeReporter mChangeReporter;
/**
* Install this class into the current process.
@@ -45,20 +48,29 @@
private AppCompatCallbacks(long[] disabledChanges) {
mDisabledChanges = Arrays.copyOf(disabledChanges, disabledChanges.length);
Arrays.sort(mDisabledChanges);
+ mChangeReporter = new ChangeReporter();
}
protected void reportChange(long changeId) {
- Log.d(TAG, "Compat change reported: " + changeId + "; UID " + Process.myUid());
- // TODO log via StatsLog
+ reportChange(changeId, StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__LOGGED);
}
protected boolean isChangeEnabled(long changeId) {
if (Arrays.binarySearch(mDisabledChanges, changeId) < 0) {
// Not present in the disabled array
- reportChange(changeId);
+ reportChange(changeId, StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__ENABLED);
return true;
}
+ reportChange(changeId, StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__DISABLED);
return false;
}
+ private void reportChange(long changeId, int state) {
+ int uid = Process.myUid();
+ //TODO(b/138374585): Implement rate limiting for the logs.
+ Log.d(TAG, ChangeReporter.createLogString(uid, changeId, state));
+ mChangeReporter.reportChange(uid, changeId,
+ state, /* source */StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__APP_PROCESS);
+ }
+
}
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index f9b96c5..fd93450 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -16,7 +16,7 @@
package android.app;
-import android.accessibilityservice.AccessibilityGestureInfo;
+import android.accessibilityservice.AccessibilityGestureEvent;
import android.accessibilityservice.AccessibilityService.Callbacks;
import android.accessibilityservice.AccessibilityService.IAccessibilityServiceClientWrapper;
import android.accessibilityservice.AccessibilityServiceInfo;
@@ -1246,7 +1246,7 @@
}
@Override
- public boolean onGesture(AccessibilityGestureInfo gestureInfo) {
+ public boolean onGesture(AccessibilityGestureEvent gestureEvent) {
/* do nothing */
return false;
}
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index 9d52363..099dea2 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -60,9 +60,7 @@
* multiple possible matching values (via {@link #addAction},
* {@link #addDataType}, {@link #addDataScheme}, {@link #addDataSchemeSpecificPart},
* {@link #addDataAuthority}, {@link #addDataPath}, and {@link #addCategory}, respectively).
- * For actions, the field
- * will not be tested if no values have been given (treating it as a wildcard);
- * if no data characteristics are specified, however, then the filter will
+ * For actions, if no data characteristics are specified, then the filter will
* only match intents that contain no data.
*
* <p>The data characteristic is
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index c74daa8..3933e81 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -519,9 +519,9 @@
* <li>The app is a system app.</li>
* <li>The app doesn't request any <a href="/guide/topics/permissions/overview">permissions</a>.
* </li>
- * <li>The <code><application></code> tag in the app's manifest doesn't contain any child
- * elements that represent
- * <a href="/guide/components/fundamentals#DeclaringComponents">app components</a>.</li>
+ * <li>The app doesn't have a <em>launcher activity</em> that is enabled by default. A launcher
+ * activity has an intent containing the <code>ACTION_MAIN</code> action and the
+ * <code>CATEGORY_LAUNCHER</code> category.</li>
* </ul>
*
* <p>Additionally, the system hides synthesized activities for some or all apps in the
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index c561013..8dfe00a 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -39,7 +39,7 @@
import android.app.admin.DevicePolicyManager;
import android.app.usage.StorageStatsManager;
import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledAfter;
+import android.compat.annotation.Disabled;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -3379,7 +3379,7 @@
* @hide
*/
@ChangeId
- @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+ @Disabled
public static final long FILTER_APPLICATION_QUERY = 135549675L;
/** {@hide} */
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index de61d70..24ee213 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -16,6 +16,7 @@
package android.content.pm;
+import android.annotation.AppIdInt;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -321,25 +322,24 @@
Bundle verificationBundle, int userId);
/**
- * Grants access to the package metadata for an ephemeral application.
+ * Grants implicit access based on an interaction between two apps. This grants the target app
+ * access to the calling application's package metadata.
* <p>
- * When an ephemeral application explicitly tries to interact with a full
- * install application [via an activity, service or provider that has been
- * exposed using the {@code visibleToInstantApp} attribute], the normal
- * application must be able to see metadata about the connecting ephemeral
- * app. If the ephemeral application uses an implicit intent [ie action VIEW,
- * category BROWSABLE], it remains hidden from the launched activity.
+ * When an application explicitly tries to interact with another application [via an
+ * activity, service or provider that is either declared in the caller's
+ * manifest via the {@code <queries>} tag or has been exposed via the target apps manifest using
+ * the {@code visibleToInstantApp} attribute], the target application must be able to see
+ * metadata about the calling app. If the calling application uses an implicit intent [ie
+ * action VIEW, category BROWSABLE], it remains hidden from the launched app.
* <p>
- * If the {@code sourceUid} is not for an ephemeral app or {@code targetUid}
- * is not for a fully installed app, this method will be a no-op.
- *
* @param userId the user
* @param intent the intent that triggered the grant
- * @param targetAppId The app ID of the fully installed application
- * @param ephemeralAppId The app ID of the ephemeral application
+ * @param callingAppId The app ID of the calling application
+ * @param targetAppId The app ID of the target application
*/
- public abstract void grantEphemeralAccess(int userId, Intent intent,
- int targetAppId, int ephemeralAppId);
+ public abstract void grantImplicitAccess(
+ @UserIdInt int userId, Intent intent, @AppIdInt int callingAppId,
+ @AppIdInt int targetAppId);
public abstract boolean isInstantAppInstallerComponent(ComponentName component);
/**
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 30e5959..76e728a 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -182,6 +182,12 @@
*/
public static final int NETWORK_STACK_UID = 1073;
+ /**
+ * Defines the UID/GID for fs-verity certificate ownership in keystore.
+ * @hide
+ */
+ public static final int FSVERITY_CERT_UID = 1075;
+
/** {@hide} */
public static final int NOBODY_UID = 9999;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 3b40c00..8b20f0b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2103,10 +2103,6 @@
private static final String TAG = "Settings";
private static final boolean LOCAL_LOGV = false;
- // Lock ensures that when enabling/disabling the master location switch, we don't end up
- // with a partial enable/disable state in multi-threaded situations.
- private static final Object mLocationSettingsLock = new Object();
-
// Used in system server calling uid workaround in call()
private static boolean sInSystemServer = false;
private static final Object sInSystemServerLock = new Object();
@@ -12920,8 +12916,7 @@
}
/**
- * Subscription to be used for voice call on a multi sim device. The supported values
- * are 0 = SUB1, 1 = SUB2 and etc.
+ * Subscription Id to be used for voice call on a multi sim device.
* @hide
*/
public static final String MULTI_SIM_VOICE_CALL_SUBSCRIPTION = "multi_sim_voice_call";
@@ -12935,15 +12930,13 @@
public static final String MULTI_SIM_VOICE_PROMPT = "multi_sim_voice_prompt";
/**
- * Subscription to be used for data call on a multi sim device. The supported values
- * are 0 = SUB1, 1 = SUB2 and etc.
+ * Subscription Id to be used for data call on a multi sim device.
* @hide
*/
public static final String MULTI_SIM_DATA_CALL_SUBSCRIPTION = "multi_sim_data_call";
/**
- * Subscription to be used for SMS on a multi sim device. The supported values
- * are 0 = SUB1, 1 = SUB2 and etc.
+ * Subscription Id to be used for SMS on a multi sim device.
* @hide
*/
public static final String MULTI_SIM_SMS_SUBSCRIPTION = "multi_sim_sms";
diff --git a/core/java/android/service/carrier/ApnService.java b/core/java/android/service/carrier/ApnService.java
index 57e4b1b..0c12fd4 100644
--- a/core/java/android/service/carrier/ApnService.java
+++ b/core/java/android/service/carrier/ApnService.java
@@ -26,7 +26,7 @@
import android.os.IBinder;
import android.util.Log;
-import com.android.internal.telephony.IApnSourceService;
+import android.service.carrier.IApnSourceService;
import java.util.List;
diff --git a/telephony/java/com/android/internal/telephony/IApnSourceService.aidl b/core/java/android/service/carrier/IApnSourceService.aidl
similarity index 93%
rename from telephony/java/com/android/internal/telephony/IApnSourceService.aidl
rename to core/java/android/service/carrier/IApnSourceService.aidl
index 34c9067..fadd2ff 100644
--- a/telephony/java/com/android/internal/telephony/IApnSourceService.aidl
+++ b/core/java/android/service/carrier/IApnSourceService.aidl
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-package com.android.internal.telephony;
+package android.service.carrier;
import android.content.ContentValues;
+/** @hide */
interface IApnSourceService {
/** Retreive APNs. */
ContentValues[] getApns(int subId);
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 78e30ab..85f13d5 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1522,6 +1522,7 @@
private ArrayList<Notification.Action> mSmartActions;
private ArrayList<CharSequence> mSmartReplies;
private boolean mCanBubble;
+ private boolean mVisuallyInterruptive;
private static final int PARCEL_VERSION = 2;
@@ -1553,6 +1554,7 @@
out.writeTypedList(mSmartActions, flags);
out.writeCharSequenceList(mSmartReplies);
out.writeBoolean(mCanBubble);
+ out.writeBoolean(mVisuallyInterruptive);
}
/** @hide */
@@ -1585,6 +1587,7 @@
mSmartActions = in.createTypedArrayList(Notification.Action.CREATOR);
mSmartReplies = in.readCharSequenceList();
mCanBubble = in.readBoolean();
+ mVisuallyInterruptive = in.readBoolean();
}
@@ -1772,6 +1775,11 @@
}
/** @hide */
+ public boolean visuallyInterruptive() {
+ return mVisuallyInterruptive;
+ }
+
+ /** @hide */
public boolean isNoisy() {
return mNoisy;
}
@@ -1787,7 +1795,8 @@
ArrayList<SnoozeCriterion> snoozeCriteria, boolean showBadge,
int userSentiment, boolean hidden, long lastAudiblyAlertedMs,
boolean noisy, ArrayList<Notification.Action> smartActions,
- ArrayList<CharSequence> smartReplies, boolean canBubble) {
+ ArrayList<CharSequence> smartReplies, boolean canBubble,
+ boolean visuallyInterruptive) {
mKey = key;
mRank = rank;
mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW;
@@ -1808,6 +1817,7 @@
mSmartActions = smartActions;
mSmartReplies = smartReplies;
mCanBubble = canBubble;
+ mVisuallyInterruptive = visuallyInterruptive;
}
/**
@@ -1832,7 +1842,8 @@
other.mNoisy,
other.mSmartActions,
other.mSmartReplies,
- other.mCanBubble);
+ other.mCanBubble,
+ other.mVisuallyInterruptive);
}
/**
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 8665106..1c50d73 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -276,6 +276,7 @@
final int runCount = mDirections.getRunCount();
for (int runIndex = 0; runIndex < runCount; runIndex++) {
final int runStart = mDirections.getRunStart(runIndex);
+ if (runStart > mLen) break;
final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
final boolean runIsRtl = mDirections.isRunRtl(runIndex);
@@ -360,6 +361,7 @@
float h = 0;
for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
final int runStart = mDirections.getRunStart(runIndex);
+ if (runStart > mLen) break;
final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
final boolean runIsRtl = mDirections.isRunRtl(runIndex);
@@ -417,6 +419,7 @@
float h = 0;
for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
final int runStart = mDirections.getRunStart(runIndex);
+ if (runStart > mLen) break;
final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
final boolean runIsRtl = mDirections.isRunRtl(runIndex);
diff --git a/core/java/android/view/IPinnedStackController.aidl b/core/java/android/view/IPinnedStackController.aidl
index f1d152b..d2dcb56 100644
--- a/core/java/android/view/IPinnedStackController.aidl
+++ b/core/java/android/view/IPinnedStackController.aidl
@@ -16,8 +16,6 @@
package android.view;
-import android.graphics.Rect;
-
/**
* An interface to the PinnedStackController to update it of state changes, and to query
* information based on the current state.
@@ -32,17 +30,15 @@
oneway void setIsMinimized(boolean isMinimized);
/**
+ * Notifies the controller of the current min edge size, this is needed to allow the system to
+ * properly calculate the aspect ratio of the expanded PIP. The given {@param minEdgeSize} is
+ * always bounded to be larger than the default minEdgeSize, so the caller can call this method
+ * with 0 to reset to the default size.
+ */
+ oneway void setMinEdgeSize(int minEdgeSize);
+
+ /**
* @return what WM considers to be the current device rotation.
*/
int getDisplayRotation();
-
- /**
- * Notifies the controller to actually start the PiP animation.
- * The bounds would be calculated based on the last save reentry fraction internally.
- * {@param destinationBounds} is the stack bounds of the final PiP window
- * and {@param sourceRectHint} is the source bounds hint used when entering picture-in-picture,
- * expect the same bound passed via IPinnedStackListener#onPrepareAnimation.
- * {@param animationDuration} suggests the animation duration transitioning to PiP window.
- */
- void startAnimation(in Rect destinationBounds, in Rect sourceRectHint, int animationDuration);
}
diff --git a/core/java/android/view/IPinnedStackListener.aidl b/core/java/android/view/IPinnedStackListener.aidl
index 806d81e..2da353b 100644
--- a/core/java/android/view/IPinnedStackListener.aidl
+++ b/core/java/android/view/IPinnedStackListener.aidl
@@ -16,10 +16,8 @@
package android.view;
-import android.content.ComponentName;
import android.content.pm.ParceledListSlice;
import android.graphics.Rect;
-import android.view.DisplayInfo;
import android.view.IPinnedStackController;
/**
@@ -38,13 +36,18 @@
/**
* Called when the window manager has detected a change that would cause the movement bounds
* to be changed (ie. after configuration change, aspect ratio change, etc). It then provides
- * the components that allow the listener to calculate the movement bounds itself.
- * The {@param animatingBounds} are provided to indicate the current target bounds of the
- * pinned stack (the final bounds if animating, the current bounds if not),
- * which may be helpful in calculating dependent animation bounds.
+ * the components that allow the listener to calculate the movement bounds itself. The
+ * {@param normalBounds} are also the default bounds that the PiP would be entered in its
+ * current state with the aspect ratio applied. The {@param animatingBounds} are provided
+ * to indicate the current target bounds of the pinned stack (the final bounds if animating,
+ * the current bounds if not), which may be helpful in calculating dependent animation bounds.
+ *
+ * The {@param displayRotation} is provided so that the client can verify when making certain
+ * calls that it will not provide stale information based on an old display rotation (ie. if
+ * the WM has changed in the mean time but the client has not received onMovementBoundsChanged).
*/
- void onMovementBoundsChanged(in Rect animatingBounds, boolean fromImeAdjustment,
- boolean fromShelfAdjustment);
+ void onMovementBoundsChanged(in Rect insetBounds, in Rect normalBounds, in Rect animatingBounds,
+ boolean fromImeAdjustment, boolean fromShelfAdjustment, int displayRotation);
/**
* Called when window manager decides to adjust the pinned stack bounds because of the IME, or
@@ -73,47 +76,4 @@
* is first registered to allow the listener to synchronized its state with the controller.
*/
void onActionsChanged(in ParceledListSlice actions);
-
- /**
- * Called by the window manager to notify the listener to save the reentry fraction,
- * typically when an Activity leaves PiP (picture-in-picture) mode to fullscreen.
- * {@param componentName} represents the application component of PiP window
- * while {@param bounds} is the current PiP bounds used to calculate the
- * reentry snap fraction.
- */
- void onSaveReentrySnapFraction(in ComponentName componentName, in Rect bounds);
-
- /**
- * Called by the window manager to notify the listener to reset saved reentry fraction,
- * typically when an Activity enters PiP (picture-in-picture) mode from fullscreen.
- * {@param componentName} represents the application component of PiP window.
- */
- void onResetReentrySnapFraction(in ComponentName componentName);
-
- /**
- * Called when the window manager has detected change on DisplayInfo, or
- * when the listener is first registered to allow the listener to synchronized its state with
- * the controller.
- */
- void onDisplayInfoChanged(in DisplayInfo displayInfo);
-
- /**
- * Called by the window manager at the beginning of a configuration update cascade
- * since the metrics from these resources are used for bounds calculations.
- */
- void onConfigurationChanged();
-
- /**
- * Called by the window manager when the aspect ratio is reset.
- */
- void onAspectRatioChanged(float aspectRatio);
-
- /**
- * Called by the window manager to notify the listener to prepare for PiP animation.
- * Internally, the target bounds would be calculated from the given {@param aspectRatio}
- * and {@param bounds}, the saved reentry snap fraction also contributes.
- * Caller would wait for a IPinnedStackController#startAnimation callback to actually
- * start the animation, see details in IPinnedStackController.
- */
- void onPrepareAnimation(in Rect sourceRectHint, float aspectRatio, in Rect bounds);
}
diff --git a/core/java/android/view/SyncRtSurfaceTransactionApplier.java b/core/java/android/view/SyncRtSurfaceTransactionApplier.java
index 85457cb..1b6c575 100644
--- a/core/java/android/view/SyncRtSurfaceTransactionApplier.java
+++ b/core/java/android/view/SyncRtSurfaceTransactionApplier.java
@@ -30,6 +30,14 @@
*/
public class SyncRtSurfaceTransactionApplier {
+ public static final int FLAG_ALL = 0xffffffff;
+ public static final int FLAG_ALPHA = 1;
+ public static final int FLAG_MATRIX = 1 << 1;
+ public static final int FLAG_WINDOW_CROP = 1 << 2;
+ public static final int FLAG_LAYER = 1 << 3;
+ public static final int FLAG_CORNER_RADIUS = 1 << 4;
+ public static final int FLAG_VISIBILITY = 1 << 5;
+
private final Surface mTargetSurface;
private final ViewRootImpl mTargetViewRootImpl;
private final float[] mTmpFloat9 = new float[9];
@@ -72,15 +80,27 @@
}
public static void applyParams(Transaction t, SurfaceParams params, float[] tmpFloat9) {
- t.setMatrix(params.surface, params.matrix, tmpFloat9);
- t.setWindowCrop(params.surface, params.windowCrop);
- t.setAlpha(params.surface, params.alpha);
- t.setLayer(params.surface, params.layer);
- t.setCornerRadius(params.surface, params.cornerRadius);
- if (params.visible) {
- t.show(params.surface);
- } else {
- t.hide(params.surface);
+ if ((params.flags & FLAG_MATRIX) != 0) {
+ t.setMatrix(params.surface, params.matrix, tmpFloat9);
+ }
+ if ((params.flags & FLAG_WINDOW_CROP) != 0) {
+ t.setWindowCrop(params.surface, params.windowCrop);
+ }
+ if ((params.flags & FLAG_ALPHA) != 0) {
+ t.setAlpha(params.surface, params.alpha);
+ }
+ if ((params.flags & FLAG_LAYER) != 0) {
+ t.setLayer(params.surface, params.layer);
+ }
+ if ((params.flags & FLAG_CORNER_RADIUS) != 0) {
+ t.setCornerRadius(params.surface, params.cornerRadius);
+ }
+ if ((params.flags & FLAG_VISIBILITY) != 0) {
+ if (params.visible) {
+ t.show(params.surface);
+ } else {
+ t.hide(params.surface);
+ }
}
}
@@ -115,17 +135,95 @@
public static class SurfaceParams {
- /**
- * Constructs surface parameters to be applied when the current view state gets pushed to
- * RenderThread.
- *
- * @param surface The surface to modify.
- * @param alpha Alpha to apply.
- * @param matrix Matrix to apply.
- * @param windowCrop Crop to apply.
- */
- public SurfaceParams(SurfaceControl surface, float alpha, Matrix matrix,
+ public static class Builder {
+ final SurfaceControl surface;
+ int flags;
+ float alpha;
+ float cornerRadius;
+ Matrix matrix;
+ Rect windowCrop;
+ int layer;
+ boolean visible;
+
+ /**
+ * @param surface The surface to modify.
+ */
+ public Builder(SurfaceControl surface) {
+ this.surface = surface;
+ }
+
+ /**
+ * @param alpha The alpha value to apply to the surface.
+ * @return this Builder
+ */
+ public Builder withAlpha(float alpha) {
+ this.alpha = alpha;
+ flags |= FLAG_ALPHA;
+ return this;
+ }
+
+ /**
+ * @param matrix The matrix to apply to the surface.
+ * @return this Builder
+ */
+ public Builder withMatrix(Matrix matrix) {
+ this.matrix = matrix;
+ flags |= FLAG_MATRIX;
+ return this;
+ }
+
+ /**
+ * @param windowCrop The window crop to apply to the surface.
+ * @return this Builder
+ */
+ public Builder withWindowCrop(Rect windowCrop) {
+ this.windowCrop = windowCrop;
+ flags |= FLAG_WINDOW_CROP;
+ return this;
+ }
+
+ /**
+ * @param layer The layer to assign the surface.
+ * @return this Builder
+ */
+ public Builder withLayer(int layer) {
+ this.layer = layer;
+ flags |= FLAG_LAYER;
+ return this;
+ }
+
+ /**
+ * @param radius the Radius for rounded corners to apply to the surface.
+ * @return this Builder
+ */
+ public Builder withCornerRadius(float radius) {
+ this.cornerRadius = radius;
+ flags |= FLAG_CORNER_RADIUS;
+ return this;
+ }
+
+ /**
+ * @param visible The visibility to apply to the surface.
+ * @return this Builder
+ */
+ public Builder withVisibility(boolean visible) {
+ this.visible = visible;
+ flags |= FLAG_VISIBILITY;
+ return this;
+ }
+
+ /**
+ * @return a new SurfaceParams instance
+ */
+ public SurfaceParams build() {
+ return new SurfaceParams(surface, flags, alpha, matrix, windowCrop, layer,
+ cornerRadius, visible);
+ }
+ }
+
+ private SurfaceParams(SurfaceControl surface, int params, float alpha, Matrix matrix,
Rect windowCrop, int layer, float cornerRadius, boolean visible) {
+ this.flags = params;
this.surface = surface;
this.alpha = alpha;
this.matrix = new Matrix(matrix);
@@ -135,6 +233,36 @@
this.visible = visible;
}
+
+ /**
+ * Constructs surface parameters to be applied when the current view state gets pushed to
+ * RenderThread.
+ *
+ * @param surface The surface to modify.
+ * @param alpha Alpha to apply.
+ * @param matrix Matrix to apply.
+ * @param windowCrop Crop to apply.
+ * @param layer The layer to apply.
+ * @param cornerRadius The corner radius to apply.
+ * @param visible The visibility to apply.
+ *
+ * @deprecated Use {@link SurfaceParams.Builder} to create an instance.
+ */
+ @Deprecated
+ public SurfaceParams(SurfaceControl surface, float alpha, Matrix matrix,
+ Rect windowCrop, int layer, float cornerRadius, boolean visible) {
+ this.flags = FLAG_ALL;
+ this.surface = surface;
+ this.alpha = alpha;
+ this.matrix = new Matrix(matrix);
+ this.windowCrop = new Rect(windowCrop);
+ this.layer = layer;
+ this.cornerRadius = cornerRadius;
+ this.visible = visible;
+ }
+
+ private final int flags;
+
@VisibleForTesting
public final SurfaceControl surface;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index bdfb3e3..cfb6a79a 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -14466,8 +14466,8 @@
}
notifyAppearedOrDisappearedForContentCaptureIfNeeded(isVisible);
- if (isVisible != oldVisible) {
- updateSystemGestureExclusionRects();
+ if (!getSystemGestureExclusionRects().isEmpty() && isVisible != oldVisible) {
+ postUpdateSystemGestureExclusionRects();
}
}
diff --git a/core/java/android/view/accessibility/AccessibilityCache.java b/core/java/android/view/accessibility/AccessibilityCache.java
index 484d9a1..dc8bf9b 100644
--- a/core/java/android/view/accessibility/AccessibilityCache.java
+++ b/core/java/android/view/accessibility/AccessibilityCache.java
@@ -71,7 +71,9 @@
private boolean mIsAllWindowsCached;
- private final SparseArray<AccessibilityWindowInfo> mWindowCache =
+ // The SparseArray of all {@link AccessibilityWindowInfo}s on all displays.
+ // The key of outer SparseArray is display ID and the key of inner SparseArray is window ID.
+ private final SparseArray<SparseArray<AccessibilityWindowInfo>> mWindowCacheByDisplay =
new SparseArray<>();
private final SparseArray<LongSparseArray<AccessibilityNodeInfo>> mNodeCache =
@@ -84,34 +86,66 @@
mAccessibilityNodeRefresher = nodeRefresher;
}
- public void setWindows(List<AccessibilityWindowInfo> windows) {
+ /**
+ * Sets all {@link AccessibilityWindowInfo}s of all displays into the cache.
+ * The key of SparseArray is display ID.
+ *
+ * @param windowsOnAllDisplays The accessibility windows of all displays.
+ */
+ public void setWindowsOnAllDisplays(
+ SparseArray<List<AccessibilityWindowInfo>> windowsOnAllDisplays) {
synchronized (mLock) {
if (DEBUG) {
Log.i(LOG_TAG, "Set windows");
}
- clearWindowCache();
- if (windows == null) {
+ clearWindowCacheLocked();
+ if (windowsOnAllDisplays == null) {
return;
}
- final int windowCount = windows.size();
- for (int i = 0; i < windowCount; i++) {
- final AccessibilityWindowInfo window = windows.get(i);
- addWindow(window);
+
+ final int displayCounts = windowsOnAllDisplays.size();
+ for (int i = 0; i < displayCounts; i++) {
+ final List<AccessibilityWindowInfo> windowsOfDisplay =
+ windowsOnAllDisplays.valueAt(i);
+
+ if (windowsOfDisplay == null) {
+ continue;
+ }
+
+ final int displayId = windowsOnAllDisplays.keyAt(i);
+ final int windowCount = windowsOfDisplay.size();
+ for (int j = 0; j < windowCount; j++) {
+ addWindowByDisplayLocked(displayId, windowsOfDisplay.get(j));
+ }
}
mIsAllWindowsCached = true;
}
}
+ /**
+ * Sets an {@link AccessibilityWindowInfo} into the cache.
+ *
+ * @param window The accessibility window.
+ */
public void addWindow(AccessibilityWindowInfo window) {
synchronized (mLock) {
if (DEBUG) {
- Log.i(LOG_TAG, "Caching window: " + window.getId());
+ Log.i(LOG_TAG, "Caching window: " + window.getId() + " at display Id [ "
+ + window.getDisplayId() + " ]");
}
- final int windowId = window.getId();
- mWindowCache.put(windowId, new AccessibilityWindowInfo(window));
+ addWindowByDisplayLocked(window.getDisplayId(), window);
}
}
+ private void addWindowByDisplayLocked(int displayId, AccessibilityWindowInfo window) {
+ SparseArray<AccessibilityWindowInfo> windows = mWindowCacheByDisplay.get(displayId);
+ if (windows == null) {
+ windows = new SparseArray<>();
+ mWindowCacheByDisplay.put(displayId, windows);
+ }
+ final int windowId = window.getId();
+ windows.put(windowId, new AccessibilityWindowInfo(window));
+ }
/**
* Notifies the cache that the something in the UI changed. As a result
* the cache will either refresh some nodes or evict some nodes.
@@ -236,44 +270,82 @@
}
}
- public List<AccessibilityWindowInfo> getWindows() {
+ /**
+ * Gets all {@link AccessibilityWindowInfo}s of all displays from the cache.
+ *
+ * @return All cached {@link AccessibilityWindowInfo}s of all displays
+ * or null if such not found. The key of SparseArray is display ID.
+ */
+ public SparseArray<List<AccessibilityWindowInfo>> getWindowsOnAllDisplays() {
synchronized (mLock) {
if (!mIsAllWindowsCached) {
return null;
}
+ final SparseArray<List<AccessibilityWindowInfo>> returnWindows = new SparseArray<>();
+ final int displayCounts = mWindowCacheByDisplay.size();
- final int windowCount = mWindowCache.size();
- if (windowCount > 0) {
- // Careful to return the windows in a decreasing layer order.
- SparseArray<AccessibilityWindowInfo> sortedWindows = mTempWindowArray;
- sortedWindows.clear();
+ if (displayCounts > 0) {
+ for (int i = 0; i < displayCounts; i++) {
+ final int displayId = mWindowCacheByDisplay.keyAt(i);
+ final SparseArray<AccessibilityWindowInfo> windowsOfDisplay =
+ mWindowCacheByDisplay.valueAt(i);
- for (int i = 0; i < windowCount; i++) {
- AccessibilityWindowInfo window = mWindowCache.valueAt(i);
- sortedWindows.put(window.getLayer(), window);
+ if (windowsOfDisplay == null) {
+ continue;
+ }
+
+ final int windowCount = windowsOfDisplay.size();
+ if (windowCount > 0) {
+ // Careful to return the windows in a decreasing layer order.
+ SparseArray<AccessibilityWindowInfo> sortedWindows = mTempWindowArray;
+ sortedWindows.clear();
+
+ for (int j = 0; j < windowCount; j++) {
+ AccessibilityWindowInfo window = windowsOfDisplay.valueAt(j);
+ sortedWindows.put(window.getLayer(), window);
+ }
+
+ // It's possible in transient conditions for two windows to share the same
+ // layer, which results in sortedWindows being smaller than
+ // mWindowCacheByDisplay
+ final int sortedWindowCount = sortedWindows.size();
+ List<AccessibilityWindowInfo> windows =
+ new ArrayList<>(sortedWindowCount);
+ for (int j = sortedWindowCount - 1; j >= 0; j--) {
+ AccessibilityWindowInfo window = sortedWindows.valueAt(j);
+ windows.add(new AccessibilityWindowInfo(window));
+ sortedWindows.removeAt(j);
+ }
+ returnWindows.put(displayId, windows);
+ }
}
-
- // It's possible in transient conditions for two windows to share the same
- // layer, which results in sortedWindows being smaller than mWindowCache
- final int sortedWindowCount = sortedWindows.size();
- List<AccessibilityWindowInfo> windows = new ArrayList<>(sortedWindowCount);
- for (int i = sortedWindowCount - 1; i >= 0; i--) {
- AccessibilityWindowInfo window = sortedWindows.valueAt(i);
- windows.add(new AccessibilityWindowInfo(window));
- sortedWindows.removeAt(i);
- }
-
- return windows;
+ return returnWindows;
}
return null;
}
}
+ /**
+ * Gets an {@link AccessibilityWindowInfo} by windowId.
+ *
+ * @param windowId The id of the window.
+ *
+ * @return The {@link AccessibilityWindowInfo} or null if such not found.
+ */
public AccessibilityWindowInfo getWindow(int windowId) {
synchronized (mLock) {
- AccessibilityWindowInfo window = mWindowCache.get(windowId);
- if (window != null) {
- return new AccessibilityWindowInfo(window);
+ final int displayCounts = mWindowCacheByDisplay.size();
+ for (int i = 0; i < displayCounts; i++) {
+ final SparseArray<AccessibilityWindowInfo> windowsOfDisplay =
+ mWindowCacheByDisplay.valueAt(i);
+ if (windowsOfDisplay == null) {
+ continue;
+ }
+
+ AccessibilityWindowInfo window = windowsOfDisplay.get(windowId);
+ if (window != null) {
+ return new AccessibilityWindowInfo(window);
+ }
}
return null;
}
@@ -358,7 +430,7 @@
if (DEBUG) {
Log.i(LOG_TAG, "clear()");
}
- clearWindowCache();
+ clearWindowCacheLocked();
final int nodesForWindowCount = mNodeCache.size();
for (int i = nodesForWindowCount - 1; i >= 0; i--) {
final int windowId = mNodeCache.keyAt(i);
@@ -370,8 +442,23 @@
}
}
- private void clearWindowCache() {
- mWindowCache.clear();
+ private void clearWindowCacheLocked() {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "clearWindowCacheLocked");
+ }
+ final int displayCounts = mWindowCacheByDisplay.size();
+
+ if (displayCounts > 0) {
+ for (int i = displayCounts - 1; i >= 0; i--) {
+ final int displayId = mWindowCacheByDisplay.keyAt(i);
+ final SparseArray<AccessibilityWindowInfo> windows =
+ mWindowCacheByDisplay.get(displayId);
+ if (windows != null) {
+ windows.clear();
+ }
+ mWindowCacheByDisplay.remove(displayId);
+ }
+ }
mIsAllWindowsCached = false;
}
@@ -444,32 +531,41 @@
public void checkIntegrity() {
synchronized (mLock) {
// Get the root.
- if (mWindowCache.size() <= 0 && mNodeCache.size() == 0) {
+ if (mWindowCacheByDisplay.size() <= 0 && mNodeCache.size() == 0) {
return;
}
AccessibilityWindowInfo focusedWindow = null;
AccessibilityWindowInfo activeWindow = null;
- final int windowCount = mWindowCache.size();
- for (int i = 0; i < windowCount; i++) {
- AccessibilityWindowInfo window = mWindowCache.valueAt(i);
+ final int displayCounts = mWindowCacheByDisplay.size();
+ for (int i = 0; i < displayCounts; i++) {
+ final SparseArray<AccessibilityWindowInfo> windowsOfDisplay =
+ mWindowCacheByDisplay.valueAt(i);
- // Check for one active window.
- if (window.isActive()) {
- if (activeWindow != null) {
- Log.e(LOG_TAG, "Duplicate active window:" + window);
- } else {
- activeWindow = window;
- }
+ if (windowsOfDisplay == null) {
+ continue;
}
- // Check for one focused window.
- if (window.isFocused()) {
- if (focusedWindow != null) {
- Log.e(LOG_TAG, "Duplicate focused window:" + window);
- } else {
- focusedWindow = window;
+ final int windowCount = windowsOfDisplay.size();
+ for (int j = 0; j < windowCount; j++) {
+ final AccessibilityWindowInfo window = windowsOfDisplay.valueAt(j);
+
+ // Check for one active window.
+ if (window.isActive()) {
+ if (activeWindow != null) {
+ Log.e(LOG_TAG, "Duplicate active window:" + window);
+ } else {
+ activeWindow = window;
+ }
+ }
+ // Check for one focused window.
+ if (window.isFocused()) {
+ if (focusedWindow != null) {
+ Log.e(LOG_TAG, "Duplicate focused window:" + window);
+ } else {
+ focusedWindow = window;
+ }
}
}
}
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 4db6f4f..d9fa9f2 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -28,6 +28,7 @@
import android.util.Log;
import android.util.LongSparseArray;
import android.util.SparseArray;
+import android.view.Display;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
@@ -267,12 +268,14 @@
try {
IAccessibilityServiceConnection connection = getConnection(connectionId);
if (connection != null) {
- List<AccessibilityWindowInfo> windows = sAccessibilityCache.getWindows();
- if (windows != null) {
+ SparseArray<List<AccessibilityWindowInfo>> allWindows =
+ sAccessibilityCache.getWindowsOnAllDisplays();
+ List<AccessibilityWindowInfo> windows;
+ if (allWindows != null) {
if (DEBUG) {
Log.i(LOG_TAG, "Windows cache hit");
}
- return windows;
+ return allWindows.valueAt(Display.DEFAULT_DISPLAY);
}
if (DEBUG) {
Log.i(LOG_TAG, "Windows cache miss");
@@ -284,7 +287,9 @@
Binder.restoreCallingIdentity(identityToken);
}
if (windows != null) {
- sAccessibilityCache.setWindows(windows);
+ allWindows = new SparseArray<>();
+ allWindows.put(Display.DEFAULT_DISPLAY, windows);
+ sAccessibilityCache.setWindowsOnAllDisplays(allWindows);
return windows;
}
} else {
diff --git a/core/java/android/view/contentcapture/ChildContentCaptureSession.java b/core/java/android/view/contentcapture/ChildContentCaptureSession.java
index b3b0b72..5e02de4 100644
--- a/core/java/android/view/contentcapture/ChildContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ChildContentCaptureSession.java
@@ -89,6 +89,11 @@
}
@Override
+ public void internalNotifySessionLifecycle(boolean started) {
+ getMainCaptureSession().notifySessionLifecycle(mId, started);
+ }
+
+ @Override
boolean isContentCaptureEnabled() {
return getMainCaptureSession().isContentCaptureEnabled();
}
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index 625fcda..cf08c18 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -439,6 +439,19 @@
public abstract void internalNotifyViewTreeEvent(boolean started);
/**
+ * Notifies the Content Capture Service that a session has paused/resumed.
+ *
+ * @param started whether session has resumed.
+ */
+ public final void notifySessionLifecycle(boolean started) {
+ if (!isContentCaptureEnabled()) return;
+
+ internalNotifySessionLifecycle(started);
+ }
+
+ abstract void internalNotifySessionLifecycle(boolean started);
+
+ /**
* Creates a {@link ViewStructure} for a "standard" view.
*
* <p>This method should be called after a visible view is laid out; the view then must populate
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 1e7440b..349ef09 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -583,6 +583,11 @@
}
@Override
+ public void internalNotifySessionLifecycle(boolean started) {
+ notifySessionLifecycle(mId, started);
+ }
+
+ @Override
boolean isContentCaptureEnabled() {
return super.isContentCaptureEnabled() && mManager.isContentCaptureEnabled();
}
@@ -637,10 +642,9 @@
sendEvent(new ContentCaptureEvent(sessionId, type), FORCE_FLUSH);
}
- /** Public because is also used by ViewRootImpl */
- public void notifySessionLifecycle(boolean started) {
+ void notifySessionLifecycle(int sessionId, boolean started) {
final int type = started ? TYPE_SESSION_RESUMED : TYPE_SESSION_PAUSED;
- sendEvent(new ContentCaptureEvent(mId, type), FORCE_FLUSH);
+ sendEvent(new ContentCaptureEvent(sessionId, type), FORCE_FLUSH);
}
void notifyContextUpdated(int sessionId, @Nullable ContentCaptureContext context) {
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index cac75cfd..57ce28e 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -2561,7 +2561,8 @@
* @return True when the TextView isFocused and has a valid zero-length selection (cursor).
*/
private boolean shouldBlink() {
- if (!isCursorVisible() || !mTextView.isFocused()) return false;
+ if (!isCursorVisible() || !mTextView.isFocused() ||
+ !mTextView.isVisibleToUser()) return false;
final int start = mTextView.getSelectionStart();
if (start < 0) return false;
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 2f44d6e..1cb1148 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -4109,7 +4109,7 @@
final int rowsCount = getCount();
final int selectionMode = getSelectionModeForAccessibility();
final CollectionInfo collectionInfo = CollectionInfo.obtain(
- rowsCount, 1, false, selectionMode);
+ -1, -1, false, selectionMode);
info.setCollectionInfo(collectionInfo);
if (rowsCount > 0) {
diff --git a/core/java/com/android/internal/compat/ChangeReporter.java b/core/java/com/android/internal/compat/ChangeReporter.java
new file mode 100644
index 0000000..1ce071b
--- /dev/null
+++ b/core/java/com/android/internal/compat/ChangeReporter.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2019 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.compat;
+
+import android.util.StatsLog;
+
+/**
+ * A helper class to report changes to stats log.
+ *
+ * @hide
+ */
+public final class ChangeReporter {
+
+ /**
+ * Transforms StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE enum to a string.
+ *
+ * @param state to transform
+ * @return a string representing the state
+ */
+ private static String stateToString(int state) {
+ switch (state) {
+ case StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__LOGGED:
+ return "LOGGED";
+ case StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__ENABLED:
+ return "ENABLED";
+ case StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__DISABLED:
+ return "DISABLED";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ /**
+ * Constructs and returns a string to be logged to logcat when a change is reported.
+ *
+ * @param uid affected by the change
+ * @param changeId the reported change id
+ * @param state of the reported change - enabled/disabled/only logged
+ * @return string to log
+ */
+ public static String createLogString(int uid, long changeId, int state) {
+ return String.format("Compat change id reported: %d; UID %d; state: %s", changeId, uid,
+ stateToString(state));
+ }
+
+ /**
+ * Report the change to stats log.
+ *
+ * @param uid affected by the change
+ * @param changeId the reported change id
+ * @param state of the reported change - enabled/disabled/only logged
+ * @param source of the logging - app process or system server
+ */
+ public void reportChange(int uid, long changeId, int state, int source) {
+ //TODO(b/138374585): Implement rate limiting for stats log.
+ StatsLog.write(StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED, uid, changeId,
+ state, source);
+ }
+}
diff --git a/core/jni/android_hardware_SoundTrigger.cpp b/core/jni/android_hardware_SoundTrigger.cpp
index 03057dc..0002f8b 100644
--- a/core/jni/android_hardware_SoundTrigger.cpp
+++ b/core/jni/android_hardware_SoundTrigger.cpp
@@ -606,12 +606,12 @@
goto exit;
}
memory = memoryDealer->allocate(offset + size);
- if (memory == 0 || memory->pointer() == NULL) {
+ if (memory == 0 || memory->unsecurePointer() == NULL) {
status = SOUNDTRIGGER_STATUS_ERROR;
goto exit;
}
- nSoundModel = (struct sound_trigger_sound_model *)memory->pointer();
+ nSoundModel = (struct sound_trigger_sound_model *)memory->unsecurePointer();
nSoundModel->type = type;
nSoundModel->uuid = nUuid;
@@ -737,18 +737,18 @@
return SOUNDTRIGGER_STATUS_ERROR;
}
sp<IMemory> memory = memoryDealer->allocate(totalSize);
- if (memory == 0 || memory->pointer() == NULL) {
+ if (memory == 0 || memory->unsecurePointer() == NULL) {
return SOUNDTRIGGER_STATUS_ERROR;
}
if (dataSize != 0) {
- memcpy((char *)memory->pointer() + sizeof(struct sound_trigger_recognition_config),
+ memcpy((char *)memory->unsecurePointer() + sizeof(struct sound_trigger_recognition_config),
nData,
dataSize);
env->ReleaseByteArrayElements(jData, nData, 0);
}
env->DeleteLocalRef(jData);
struct sound_trigger_recognition_config *config =
- (struct sound_trigger_recognition_config *)memory->pointer();
+ (struct sound_trigger_recognition_config *)memory->unsecurePointer();
config->data_size = dataSize;
config->data_offset = sizeof(struct sound_trigger_recognition_config);
config->capture_requested = env->GetBooleanField(jConfig,
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index daa6347..c5049ec 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -649,7 +649,7 @@
if ((size_t)sizeInBytes > track->sharedBuffer()->size()) {
sizeInBytes = track->sharedBuffer()->size();
}
- memcpy(track->sharedBuffer()->pointer(), data + offsetInSamples, sizeInBytes);
+ memcpy(track->sharedBuffer()->unsecurePointer(), data + offsetInSamples, sizeInBytes);
written = sizeInBytes;
}
if (written >= 0) {
diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp
index cbae2da..b6427c9a 100644
--- a/core/jni/android_os_HwBinder.cpp
+++ b/core/jni/android_os_HwBinder.cpp
@@ -152,7 +152,7 @@
uint32_t flags,
TransactCallback callback) {
JNIEnv *env = AndroidRuntime::getJNIEnv();
- bool isOneway = (flags & TF_ONE_WAY) != 0;
+ bool isOneway = (flags & IBinder::FLAG_ONEWAY) != 0;
ScopedLocalRef<jobject> replyObj(env, nullptr);
sp<JHwParcel> replyContext = nullptr;
diff --git a/core/jni/android_view_InputChannel.cpp b/core/jni/android_view_InputChannel.cpp
index 8553a2c..af34e7b7 100644
--- a/core/jni/android_view_InputChannel.cpp
+++ b/core/jni/android_view_InputChannel.cpp
@@ -16,6 +16,7 @@
#define LOG_TAG "InputChannel-JNI"
+#include "android-base/stringprintf.h"
#include <nativehelper/JNIHelp.h>
#include "nativehelper/scoped_utf_chars.h"
#include <android_runtime/AndroidRuntime.h>
@@ -60,7 +61,7 @@
// ----------------------------------------------------------------------------
NativeInputChannel::NativeInputChannel(const sp<InputChannel>& inputChannel) :
- mInputChannel(inputChannel), mDisposeCallback(NULL) {
+ mInputChannel(inputChannel), mDisposeCallback(nullptr) {
}
NativeInputChannel::~NativeInputChannel() {
@@ -74,8 +75,8 @@
void NativeInputChannel::invokeAndRemoveDisposeCallback(JNIEnv* env, jobject obj) {
if (mDisposeCallback) {
mDisposeCallback(env, obj, mInputChannel, mDisposeData);
- mDisposeCallback = NULL;
- mDisposeData = NULL;
+ mDisposeCallback = nullptr;
+ mDisposeData = nullptr;
}
}
@@ -96,14 +97,14 @@
sp<InputChannel> android_view_InputChannel_getInputChannel(JNIEnv* env, jobject inputChannelObj) {
NativeInputChannel* nativeInputChannel =
android_view_InputChannel_getNativeInputChannel(env, inputChannelObj);
- return nativeInputChannel != NULL ? nativeInputChannel->getInputChannel() : NULL;
+ return nativeInputChannel != nullptr ? nativeInputChannel->getInputChannel() : nullptr;
}
void android_view_InputChannel_setDisposeCallback(JNIEnv* env, jobject inputChannelObj,
InputChannelObjDisposeCallback callback, void* data) {
NativeInputChannel* nativeInputChannel =
android_view_InputChannel_getNativeInputChannel(env, inputChannelObj);
- if (nativeInputChannel == NULL) {
+ if (nativeInputChannel == nullptr) {
ALOGW("Cannot set dispose callback because input channel object has not been initialized.");
} else {
nativeInputChannel->setDisposeCallback(callback, data);
@@ -131,27 +132,27 @@
status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);
if (result) {
- String8 message;
- message.appendFormat("Could not open input channel pair. status=%d", result);
- jniThrowRuntimeException(env, message.string());
- return NULL;
+ std::string message = android::base::StringPrintf(
+ "Could not open input channel pair : %s", strerror(-result));
+ jniThrowRuntimeException(env, message.c_str());
+ return nullptr;
}
- jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, NULL);
+ jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, nullptr);
if (env->ExceptionCheck()) {
- return NULL;
+ return nullptr;
}
jobject serverChannelObj = android_view_InputChannel_createInputChannel(env,
std::make_unique<NativeInputChannel>(serverChannel));
if (env->ExceptionCheck()) {
- return NULL;
+ return nullptr;
}
jobject clientChannelObj = android_view_InputChannel_createInputChannel(env,
std::make_unique<NativeInputChannel>(clientChannel));
if (env->ExceptionCheck()) {
- return NULL;
+ return nullptr;
}
env->SetObjectArrayElement(channelPair, 0, serverChannelObj);
@@ -170,7 +171,7 @@
nativeInputChannel->invokeAndRemoveDisposeCallback(env, obj);
- android_view_InputChannel_setNativeInputChannel(env, obj, NULL);
+ android_view_InputChannel_setNativeInputChannel(env, obj, nullptr);
delete nativeInputChannel;
}
}
@@ -179,14 +180,14 @@
NativeInputChannel* nativeInputChannel =
android_view_InputChannel_getNativeInputChannel(env, obj);
if (nativeInputChannel) {
- android_view_InputChannel_setNativeInputChannel(env, obj, NULL);
+ android_view_InputChannel_setNativeInputChannel(env, obj, nullptr);
delete nativeInputChannel;
}
}
static void android_view_InputChannel_nativeTransferTo(JNIEnv* env, jobject obj,
jobject otherObj) {
- if (android_view_InputChannel_getNativeInputChannel(env, otherObj) != NULL) {
+ if (android_view_InputChannel_getNativeInputChannel(env, otherObj) != nullptr) {
jniThrowException(env, "java/lang/IllegalStateException",
"Other object already has a native input channel.");
return;
@@ -195,12 +196,12 @@
NativeInputChannel* nativeInputChannel =
android_view_InputChannel_getNativeInputChannel(env, obj);
android_view_InputChannel_setNativeInputChannel(env, otherObj, nativeInputChannel);
- android_view_InputChannel_setNativeInputChannel(env, obj, NULL);
+ android_view_InputChannel_setNativeInputChannel(env, obj, nullptr);
}
static void android_view_InputChannel_nativeReadFromParcel(JNIEnv* env, jobject obj,
jobject parcelObj) {
- if (android_view_InputChannel_getNativeInputChannel(env, obj) != NULL) {
+ if (android_view_InputChannel_getNativeInputChannel(env, obj) != nullptr) {
jniThrowException(env, "java/lang/IllegalStateException",
"This object already has a native input channel.");
return;
@@ -222,25 +223,26 @@
static void android_view_InputChannel_nativeWriteToParcel(JNIEnv* env, jobject obj,
jobject parcelObj) {
Parcel* parcel = parcelForJavaObject(env, parcelObj);
- if (parcel) {
- NativeInputChannel* nativeInputChannel =
- android_view_InputChannel_getNativeInputChannel(env, obj);
- if (nativeInputChannel) {
- sp<InputChannel> inputChannel = nativeInputChannel->getInputChannel();
-
- parcel->writeInt32(1);
- inputChannel->write(*parcel);
- } else {
- parcel->writeInt32(0);
- }
+ if (parcel == nullptr) {
+ ALOGE("Could not obtain parcel for Java object");
+ return;
}
+
+ NativeInputChannel* nativeInputChannel =
+ android_view_InputChannel_getNativeInputChannel(env, obj);
+ if (!nativeInputChannel) {
+ parcel->writeInt32(0); // not initialized
+ return;
+ }
+ parcel->writeInt32(1); // initialized
+ nativeInputChannel->getInputChannel()->write(*parcel);
}
static jstring android_view_InputChannel_nativeGetName(JNIEnv* env, jobject obj) {
NativeInputChannel* nativeInputChannel =
android_view_InputChannel_getNativeInputChannel(env, obj);
if (! nativeInputChannel) {
- return NULL;
+ return nullptr;
}
jstring name = env->NewStringUTF(nativeInputChannel->getInputChannel()->getName().c_str());
@@ -250,10 +252,24 @@
static void android_view_InputChannel_nativeDup(JNIEnv* env, jobject obj, jobject otherObj) {
NativeInputChannel* nativeInputChannel =
android_view_InputChannel_getNativeInputChannel(env, obj);
- if (nativeInputChannel) {
- android_view_InputChannel_setNativeInputChannel(env, otherObj,
- new NativeInputChannel(nativeInputChannel->getInputChannel()->dup()));
+ if (nativeInputChannel == nullptr) {
+ jniThrowRuntimeException(env, "InputChannel has no valid NativeInputChannel");
+ return;
}
+
+ sp<InputChannel> inputChannel = nativeInputChannel->getInputChannel();
+ if (inputChannel == nullptr) {
+ jniThrowRuntimeException(env, "NativeInputChannel has no corresponding InputChannel");
+ return;
+ }
+ sp<InputChannel> dupInputChannel = inputChannel->dup();
+ if (dupInputChannel == nullptr) {
+ std::string message = android::base::StringPrintf(
+ "Could not duplicate input channel %s", inputChannel->getName().c_str());
+ jniThrowRuntimeException(env, message.c_str());
+ }
+ android_view_InputChannel_setNativeInputChannel(env, otherObj,
+ new NativeInputChannel(dupInputChannel));
}
static jobject android_view_InputChannel_nativeGetToken(JNIEnv* env, jobject obj) {
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 1670d49..a4c504b 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -43,7 +43,6 @@
libs: [
"android.test.runner",
- "telephony-common",
"testables",
"org.apache.http.legacy",
"android.test.base",
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
index 1a22a70..6bce651 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
@@ -29,6 +29,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.util.SparseArray;
+import android.view.Display;
import android.view.View;
import androidx.test.filters.LargeTest;
@@ -51,12 +53,17 @@
public class AccessibilityCacheTest {
private static final int WINDOW_ID_1 = 0xBEEF;
private static final int WINDOW_ID_2 = 0xFACE;
+ private static final int WINDOW_ID_3 = 0xABCD;
+ private static final int WINDOW_ID_4 = 0xDCBA;
private static final int SINGLE_VIEW_ID = 0xCAFE;
private static final int OTHER_VIEW_ID = 0xCAB2;
private static final int PARENT_VIEW_ID = 0xFED4;
private static final int CHILD_VIEW_ID = 0xFEED;
private static final int OTHER_CHILD_VIEW_ID = 0xACE2;
private static final int MOCK_CONNECTION_ID = 1;
+ private static final int SECONDARY_DISPLAY_ID = Display.DEFAULT_DISPLAY + 1;
+ private static final int DEFAULT_WINDOW_LAYER = 0;
+ private static final int SPECIFIC_WINDOW_LAYER = 5;
AccessibilityCache mAccessibilityCache;
AccessibilityCache.AccessibilityNodeRefresher mAccessibilityNodeRefresher;
@@ -70,7 +77,7 @@
@After
public void tearDown() {
- // Make sure we're recycling all of our window and node infos
+ // Make sure we're recycling all of our window and node infos.
mAccessibilityCache.clear();
AccessibilityInteractionClient.getInstance().clearCache();
}
@@ -78,7 +85,7 @@
@Test
public void testEmptyCache_returnsNull() {
assertNull(mAccessibilityCache.getNode(0, 0));
- assertNull(mAccessibilityCache.getWindows());
+ assertNull(mAccessibilityCache.getWindowsOnAllDisplays());
assertNull(mAccessibilityCache.getWindow(0));
}
@@ -114,10 +121,11 @@
try {
windowInfo = AccessibilityWindowInfo.obtain();
windowInfo.setId(WINDOW_ID_1);
+ windowInfo.setDisplayId(Display.DEFAULT_DISPLAY);
mAccessibilityCache.addWindow(windowInfo);
// Make a copy
copyOfInfo = AccessibilityWindowInfo.obtain(windowInfo);
- windowInfo.setId(WINDOW_ID_2); // Simulate recycling and reusing the original info
+ windowInfo.setId(WINDOW_ID_2); // Simulate recycling and reusing the original info.
windowFromCache = mAccessibilityCache.getWindow(WINDOW_ID_1);
assertEquals(copyOfInfo, windowFromCache);
} finally {
@@ -129,39 +137,40 @@
@Test
public void addWindowThenClear_noLongerInCache() {
- putWindowWithIdInCache(WINDOW_ID_1);
+ putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_1, Display.DEFAULT_DISPLAY,
+ DEFAULT_WINDOW_LAYER);
mAccessibilityCache.clear();
assertNull(mAccessibilityCache.getWindow(WINDOW_ID_1));
}
@Test
public void addWindowGetOtherId_returnsNull() {
- putWindowWithIdInCache(WINDOW_ID_1);
+ putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_1, Display.DEFAULT_DISPLAY,
+ DEFAULT_WINDOW_LAYER);
assertNull(mAccessibilityCache.getWindow(WINDOW_ID_1 + 1));
}
@Test
public void addWindowThenGetWindows_returnsNull() {
- putWindowWithIdInCache(WINDOW_ID_1);
- assertNull(mAccessibilityCache.getWindows());
+ putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_1, Display.DEFAULT_DISPLAY,
+ DEFAULT_WINDOW_LAYER);
+ assertNull(mAccessibilityCache.getWindowsOnAllDisplays());
}
@Test
public void setWindowsThenGetWindows_returnsInDecreasingLayerOrder() {
- AccessibilityWindowInfo windowInfo1 = null, windowInfo2 = null;
- AccessibilityWindowInfo window1Out = null, window2Out = null;
+ AccessibilityWindowInfo windowInfo1 = null;
+ AccessibilityWindowInfo windowInfo2 = null;
+ AccessibilityWindowInfo window1Out = null;
+ AccessibilityWindowInfo window2Out = null;
List<AccessibilityWindowInfo> windowsOut = null;
try {
- windowInfo1 = AccessibilityWindowInfo.obtain();
- windowInfo1.setId(WINDOW_ID_1);
- windowInfo1.setLayer(5);
- windowInfo2 = AccessibilityWindowInfo.obtain();
- windowInfo2.setId(WINDOW_ID_2);
- windowInfo2.setLayer(windowInfo1.getLayer() + 1);
+ windowInfo1 = obtainAccessibilityWindowInfo(WINDOW_ID_1, SPECIFIC_WINDOW_LAYER);
+ windowInfo2 = obtainAccessibilityWindowInfo(WINDOW_ID_2, windowInfo1.getLayer() + 1);
List<AccessibilityWindowInfo> windowsIn = Arrays.asList(windowInfo1, windowInfo2);
- mAccessibilityCache.setWindows(windowsIn);
+ setWindowsByDisplay(Display.DEFAULT_DISPLAY, windowsIn);
- windowsOut = mAccessibilityCache.getWindows();
+ windowsOut = getWindowsByDisplay(Display.DEFAULT_DISPLAY);
window1Out = mAccessibilityCache.getWindow(WINDOW_ID_1);
window2Out = mAccessibilityCache.getWindow(WINDOW_ID_2);
@@ -182,8 +191,151 @@
}
@Test
+ public void setWindowsAndAddWindow_thenGetWindows_returnsInDecreasingLayerOrder() {
+ AccessibilityWindowInfo windowInfo1 = null;
+ AccessibilityWindowInfo windowInfo2 = null;
+ AccessibilityWindowInfo window1Out = null;
+ AccessibilityWindowInfo window2Out = null;
+ AccessibilityWindowInfo window3Out = null;
+ List<AccessibilityWindowInfo> windowsOut = null;
+ try {
+ windowInfo1 = obtainAccessibilityWindowInfo(WINDOW_ID_1, SPECIFIC_WINDOW_LAYER);
+ windowInfo2 = obtainAccessibilityWindowInfo(WINDOW_ID_2, windowInfo1.getLayer() + 2);
+ List<AccessibilityWindowInfo> windowsIn = Arrays.asList(windowInfo1, windowInfo2);
+ setWindowsByDisplay(Display.DEFAULT_DISPLAY, windowsIn);
+
+ putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_3, Display.DEFAULT_DISPLAY,
+ windowInfo1.getLayer() + 1);
+
+ windowsOut = getWindowsByDisplay(Display.DEFAULT_DISPLAY);
+ window1Out = mAccessibilityCache.getWindow(WINDOW_ID_1);
+ window2Out = mAccessibilityCache.getWindow(WINDOW_ID_2);
+ window3Out = mAccessibilityCache.getWindow(WINDOW_ID_3);
+
+ assertEquals(3, windowsOut.size());
+ assertEquals(windowInfo2, windowsOut.get(0));
+ assertEquals(windowInfo1, windowsOut.get(2));
+ assertEquals(windowInfo1, window1Out);
+ assertEquals(windowInfo2, window2Out);
+ assertEquals(window3Out, windowsOut.get(1));
+ } finally {
+ window1Out.recycle();
+ window2Out.recycle();
+ window3Out.recycle();
+ windowInfo1.recycle();
+ windowInfo2.recycle();
+ for (AccessibilityWindowInfo windowInfo : windowsOut) {
+ windowInfo.recycle();
+ }
+ }
+ }
+
+ @Test
+ public void
+ setWindowsAtFirstDisplay_thenAddWindowAtSecondDisplay_returnWindowLayerOrderUnchange() {
+ AccessibilityWindowInfo windowInfo1 = null;
+ AccessibilityWindowInfo windowInfo2 = null;
+ AccessibilityWindowInfo window1Out = null;
+ AccessibilityWindowInfo window2Out = null;
+ List<AccessibilityWindowInfo> windowsOut = null;
+ try {
+ // Sets windows to default display.
+ windowInfo1 = obtainAccessibilityWindowInfo(WINDOW_ID_1, SPECIFIC_WINDOW_LAYER);
+ windowInfo2 = obtainAccessibilityWindowInfo(WINDOW_ID_2, windowInfo1.getLayer() + 2);
+ List<AccessibilityWindowInfo> windowsIn = Arrays.asList(windowInfo1, windowInfo2);
+ setWindowsByDisplay(Display.DEFAULT_DISPLAY, windowsIn);
+ // Adds one window to second display.
+ putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_3, SECONDARY_DISPLAY_ID,
+ windowInfo1.getLayer() + 1);
+
+ windowsOut = getWindowsByDisplay(Display.DEFAULT_DISPLAY);
+ window1Out = mAccessibilityCache.getWindow(WINDOW_ID_1);
+ window2Out = mAccessibilityCache.getWindow(WINDOW_ID_2);
+
+ assertEquals(2, windowsOut.size());
+ assertEquals(windowInfo2, windowsOut.get(0));
+ assertEquals(windowInfo1, windowsOut.get(1));
+ assertEquals(windowInfo1, window1Out);
+ assertEquals(windowInfo2, window2Out);
+ } finally {
+ window1Out.recycle();
+ window2Out.recycle();
+ windowInfo1.recycle();
+ windowInfo2.recycle();
+ for (AccessibilityWindowInfo windowInfo : windowsOut) {
+ windowInfo.recycle();
+ }
+ }
+ }
+
+ @Test
+ public void setWindowsAtTwoDisplays_thenGetWindows_returnsInDecreasingLayerOrder() {
+ AccessibilityWindowInfo windowInfo1 = null;
+ AccessibilityWindowInfo windowInfo2 = null;
+ AccessibilityWindowInfo window1Out = null;
+ AccessibilityWindowInfo window2Out = null;
+ AccessibilityWindowInfo windowInfo3 = null;
+ AccessibilityWindowInfo windowInfo4 = null;
+ AccessibilityWindowInfo window3Out = null;
+ AccessibilityWindowInfo window4Out = null;
+ List<AccessibilityWindowInfo> windowsOut1 = null;
+ List<AccessibilityWindowInfo> windowsOut2 = null;
+ try {
+ // Prepares all windows for default display.
+ windowInfo1 = obtainAccessibilityWindowInfo(WINDOW_ID_1, SPECIFIC_WINDOW_LAYER);
+ windowInfo2 = obtainAccessibilityWindowInfo(WINDOW_ID_2, windowInfo1.getLayer() + 1);
+ List<AccessibilityWindowInfo> windowsIn1 = Arrays.asList(windowInfo1, windowInfo2);
+ // Prepares all windows for second display.
+ windowInfo3 = obtainAccessibilityWindowInfo(WINDOW_ID_3, windowInfo1.getLayer() + 2);
+ windowInfo4 = obtainAccessibilityWindowInfo(WINDOW_ID_4, windowInfo1.getLayer() + 3);
+ List<AccessibilityWindowInfo> windowsIn2 = Arrays.asList(windowInfo3, windowInfo4);
+ // Sets all windows of all displays into A11y cache.
+ SparseArray<List<AccessibilityWindowInfo>> allWindows = new SparseArray<>();
+ allWindows.put(Display.DEFAULT_DISPLAY, windowsIn1);
+ allWindows.put(SECONDARY_DISPLAY_ID, windowsIn2);
+ mAccessibilityCache.setWindowsOnAllDisplays(allWindows);
+ // Gets windows at default display.
+ windowsOut1 = getWindowsByDisplay(Display.DEFAULT_DISPLAY);
+ window1Out = mAccessibilityCache.getWindow(WINDOW_ID_1);
+ window2Out = mAccessibilityCache.getWindow(WINDOW_ID_2);
+
+ assertEquals(2, windowsOut1.size());
+ assertEquals(windowInfo2, windowsOut1.get(0));
+ assertEquals(windowInfo1, windowsOut1.get(1));
+ assertEquals(windowInfo1, window1Out);
+ assertEquals(windowInfo2, window2Out);
+ // Gets windows at seocnd display.
+ windowsOut2 = getWindowsByDisplay(SECONDARY_DISPLAY_ID);
+ window3Out = mAccessibilityCache.getWindow(WINDOW_ID_3);
+ window4Out = mAccessibilityCache.getWindow(WINDOW_ID_4);
+
+ assertEquals(2, windowsOut2.size());
+ assertEquals(windowInfo4, windowsOut2.get(0));
+ assertEquals(windowInfo3, windowsOut2.get(1));
+ assertEquals(windowInfo3, window3Out);
+ assertEquals(windowInfo4, window4Out);
+ } finally {
+ window1Out.recycle();
+ window2Out.recycle();
+ windowInfo1.recycle();
+ windowInfo2.recycle();
+ window3Out.recycle();
+ window4Out.recycle();
+ windowInfo3.recycle();
+ windowInfo4.recycle();
+ for (AccessibilityWindowInfo windowInfo : windowsOut1) {
+ windowInfo.recycle();
+ }
+ for (AccessibilityWindowInfo windowInfo : windowsOut2) {
+ windowInfo.recycle();
+ }
+ }
+ }
+
+ @Test
public void addWindowThenStateChangedEvent_noLongerInCache() {
- putWindowWithIdInCache(WINDOW_ID_1);
+ putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_1, Display.DEFAULT_DISPLAY,
+ DEFAULT_WINDOW_LAYER);
mAccessibilityCache.onAccessibilityEvent(
AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED));
assertNull(mAccessibilityCache.getWindow(WINDOW_ID_1));
@@ -191,7 +343,8 @@
@Test
public void addWindowThenWindowsChangedEvent_noLongerInCache() {
- putWindowWithIdInCache(WINDOW_ID_1);
+ putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_1, Display.DEFAULT_DISPLAY,
+ DEFAULT_WINDOW_LAYER);
mAccessibilityCache.onAccessibilityEvent(
AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOWS_CHANGED));
assertNull(mAccessibilityCache.getWindow(WINDOW_ID_1));
@@ -622,9 +775,16 @@
}
}
- private void putWindowWithIdInCache(int id) {
+ private AccessibilityWindowInfo obtainAccessibilityWindowInfo(int windowId, int layer) {
AccessibilityWindowInfo windowInfo = AccessibilityWindowInfo.obtain();
- windowInfo.setId(id);
+ windowInfo.setId(windowId);
+ windowInfo.setLayer(layer);
+ return windowInfo;
+ }
+
+ private void putWindowWithWindowIdAndDisplayIdInCache(int windowId, int displayId, int layer) {
+ AccessibilityWindowInfo windowInfo = obtainAccessibilityWindowInfo(windowId, layer);
+ windowInfo.setDisplayId(displayId);
mAccessibilityCache.addWindow(windowInfo);
windowInfo.recycle();
}
@@ -713,4 +873,20 @@
}
}
}
+
+ private void setWindowsByDisplay(int displayId, List<AccessibilityWindowInfo> windows) {
+ SparseArray<List<AccessibilityWindowInfo>> allWindows = new SparseArray<>();
+ allWindows.put(displayId, windows);
+ mAccessibilityCache.setWindowsOnAllDisplays(allWindows);
+ }
+
+ private List<AccessibilityWindowInfo> getWindowsByDisplay(int displayId) {
+ final SparseArray<List<AccessibilityWindowInfo>> allWindows =
+ mAccessibilityCache.getWindowsOnAllDisplays();
+
+ if (allWindows != null && allWindows.size() > 0) {
+ return allWindows.get(displayId);
+ }
+ return null;
+ }
}
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
index 81ce15a..c5da549 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
@@ -162,6 +162,11 @@
}
@Override
+ public void internalNotifySessionLifecycle(boolean started) {
+ throw new UnsupportedOperationException("Should not have been called");
+ }
+
+ @Override
public void updateContentCaptureContext(ContentCaptureContext context) {
throw new UnsupportedOperationException("should not have been called");
}
diff --git a/core/tests/coretests/src/android/view/textclassifier/intent/LegacyIntentClassificationFactoryTest.java b/core/tests/coretests/src/android/view/textclassifier/intent/LegacyIntentClassificationFactoryTest.java
index e1ccd75..8891d3f 100644
--- a/core/tests/coretests/src/android/view/textclassifier/intent/LegacyIntentClassificationFactoryTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/intent/LegacyIntentClassificationFactoryTest.java
@@ -65,8 +65,10 @@
null,
null,
null,
- 0,
- 0);
+ null,
+ 0L,
+ 0L,
+ 0d);
List<LabeledIntent> intents = mLegacyIntentClassificationFactory.create(
InstrumentationRegistry.getContext(),
@@ -101,8 +103,10 @@
null,
null,
null,
- 0,
- 0);
+ null,
+ 0L,
+ 0L,
+ 0d);
List<LabeledIntent> intents = mLegacyIntentClassificationFactory.create(
InstrumentationRegistry.getContext(),
diff --git a/core/tests/coretests/src/android/view/textclassifier/intent/TemplateClassificationIntentFactoryTest.java b/core/tests/coretests/src/android/view/textclassifier/intent/TemplateClassificationIntentFactoryTest.java
index b9a1a8c..bcea5fe 100644
--- a/core/tests/coretests/src/android/view/textclassifier/intent/TemplateClassificationIntentFactoryTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/intent/TemplateClassificationIntentFactoryTest.java
@@ -83,9 +83,11 @@
null,
null,
null,
+ null,
createRemoteActionTemplates(),
- 0,
- 0);
+ 0L,
+ 0L,
+ 0d);
List<LabeledIntent> intents =
mTemplateClassificationIntentFactory.create(
@@ -124,9 +126,11 @@
null,
null,
null,
+ null,
createRemoteActionTemplates(),
- 0,
- 0);
+ 0L,
+ 0L,
+ 0d);
List<LabeledIntent> intents =
mTemplateClassificationIntentFactory.create(
@@ -162,8 +166,10 @@
null,
null,
null,
- 0,
- 0);
+ null,
+ 0L,
+ 0L,
+ 0d);
mTemplateClassificationIntentFactory.create(
InstrumentationRegistry.getContext(),
@@ -196,9 +202,11 @@
null,
null,
null,
+ null,
new RemoteActionTemplate[0],
- 0,
- 0);
+ 0L,
+ 0L,
+ 0d);
mTemplateClassificationIntentFactory.create(
InstrumentationRegistry.getContext(),
diff --git a/keystore/java/android/security/Credentials.java b/keystore/java/android/security/Credentials.java
index 08f4176..54995ac 100644
--- a/keystore/java/android/security/Credentials.java
+++ b/keystore/java/android/security/Credentials.java
@@ -16,11 +16,12 @@
package android.security;
+import android.annotation.UnsupportedAppUsage;
+
import com.android.org.bouncycastle.util.io.pem.PemObject;
import com.android.org.bouncycastle.util.io.pem.PemReader;
import com.android.org.bouncycastle.util.io.pem.PemWriter;
-import android.annotation.UnsupportedAppUsage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -90,9 +91,9 @@
public static final String EXTRA_INSTALL_AS_UID = "install_as_uid";
/**
- * Intent extra: name for the user's private key.
+ * Intent extra: name for the user's key pair.
*/
- public static final String EXTRA_USER_PRIVATE_KEY_NAME = "user_private_key_name";
+ public static final String EXTRA_USER_KEY_ALIAS = "user_key_pair_name";
/**
* Intent extra: data for the user's private key in PEM-encoded PKCS#8.
@@ -100,21 +101,11 @@
public static final String EXTRA_USER_PRIVATE_KEY_DATA = "user_private_key_data";
/**
- * Intent extra: name for the user's certificate.
- */
- public static final String EXTRA_USER_CERTIFICATE_NAME = "user_certificate_name";
-
- /**
* Intent extra: data for the user's certificate in PEM-encoded X.509.
*/
public static final String EXTRA_USER_CERTIFICATE_DATA = "user_certificate_data";
/**
- * Intent extra: name for CA certificate chain
- */
- public static final String EXTRA_CA_CERTIFICATES_NAME = "ca_certificates_name";
-
- /**
* Intent extra: data for CA certificate chain in PEM-encoded X.509.
*/
public static final String EXTRA_CA_CERTIFICATES_DATA = "ca_certificates_data";
diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl
index b3cdff7..97da3cc 100644
--- a/keystore/java/android/security/IKeyChainService.aidl
+++ b/keystore/java/android/security/IKeyChainService.aidl
@@ -43,7 +43,8 @@
String installCaCertificate(in byte[] caCertificate);
// APIs used by DevicePolicyManager
- boolean installKeyPair(in byte[] privateKey, in byte[] userCert, in byte[] certChain, String alias);
+ boolean installKeyPair(
+ in byte[] privateKey, in byte[] userCert, in byte[] certChain, String alias, int uid);
boolean removeKeyPair(String alias);
// APIs used by Settings
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 55583d5..5b53565 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -62,6 +62,7 @@
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
@@ -1451,6 +1452,37 @@
}
/**
+ * Returns whether ExifInterface currently supports parsing data from the specified mime type
+ * or not.
+ *
+ * @param mimeType the string value of mime type
+ */
+ public static boolean isSupportedMimeType(@NonNull String mimeType) {
+ if (mimeType == null) {
+ throw new NullPointerException("mimeType shouldn't be null");
+ }
+
+ switch (mimeType.toLowerCase(Locale.ROOT)) {
+ case "image/jpeg":
+ case "image/x-adobe-dng":
+ case "image/x-canon-cr2":
+ case "image/x-nikon-nef":
+ case "image/x-nikon-nrw":
+ case "image/x-sony-arw":
+ case "image/x-panasonic-rw2":
+ case "image/x-olympus-orf":
+ case "image/x-pentax-pef":
+ case "image/x-samsung-srw":
+ case "image/x-fuji-raf":
+ case "image/heic":
+ case "image/heif":
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
* Returns the EXIF attribute of the specified tag or {@code null} if there is no such tag in
* the image file.
*
diff --git a/media/jni/android_media_MediaDataSource.cpp b/media/jni/android_media_MediaDataSource.cpp
index 8c38d88..9705b91 100644
--- a/media/jni/android_media_MediaDataSource.cpp
+++ b/media/jni/android_media_MediaDataSource.cpp
@@ -106,7 +106,8 @@
}
ALOGV("readAt %lld / %zu => %d.", (long long)offset, size, numread);
- env->GetByteArrayRegion(mByteArrayObj, 0, numread, (jbyte*)mMemory->pointer());
+ env->GetByteArrayRegion(mByteArrayObj, 0, numread,
+ (jbyte*)mMemory->unsecurePointer());
return numread;
}
diff --git a/media/jni/android_media_MediaDescrambler.cpp b/media/jni/android_media_MediaDescrambler.cpp
index aa79ce0..c61365a 100644
--- a/media/jni/android_media_MediaDescrambler.cpp
+++ b/media/jni/android_media_MediaDescrambler.cpp
@@ -220,7 +220,7 @@
return NO_MEMORY;
}
- memcpy(mMem->pointer(),
+ memcpy(mMem->unsecurePointer(),
(const void*)((const uint8_t*)srcPtr + srcOffset), totalLength);
DestinationBuffer dstBuffer;
@@ -248,7 +248,8 @@
if (*status == Status::OK) {
if (*bytesWritten > 0 && (ssize_t) *bytesWritten <= totalLength) {
- memcpy((void*)((uint8_t*)dstPtr + dstOffset), mMem->pointer(), *bytesWritten);
+ memcpy((void*)((uint8_t*)dstPtr + dstOffset), mMem->unsecurePointer(),
+ *bytesWritten);
} else {
// status seems OK but bytesWritten is invalid, we really
// have no idea what is wrong.
diff --git a/media/jni/android_media_MediaHTTPConnection.cpp b/media/jni/android_media_MediaHTTPConnection.cpp
index 365e045..53adff3 100644
--- a/media/jni/android_media_MediaHTTPConnection.cpp
+++ b/media/jni/android_media_MediaHTTPConnection.cpp
@@ -148,7 +148,7 @@
byteArrayObj,
0,
n,
- (jbyte *)conn->getIMemory()->pointer());
+ (jbyte *)conn->getIMemory()->unsecurePointer());
}
return n;
diff --git a/media/jni/android_media_MediaMetadataRetriever.cpp b/media/jni/android_media_MediaMetadataRetriever.cpp
index 3809bc4..18fd1a0 100644
--- a/media/jni/android_media_MediaMetadataRetriever.cpp
+++ b/media/jni/android_media_MediaMetadataRetriever.cpp
@@ -396,8 +396,12 @@
// Call native method to retrieve a video frame
VideoFrame *videoFrame = NULL;
sp<IMemory> frameMemory = retriever->getFrameAtTime(timeUs, option);
+ // TODO: Using unsecurePointer() has some associated security pitfalls
+ // (see declaration for details).
+ // Either document why it is safe in this case or address the
+ // issue (e.g. by copying).
if (frameMemory != 0) { // cast the shared structure to a VideoFrame object
- videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());
+ videoFrame = static_cast<VideoFrame *>(frameMemory->unsecurePointer());
}
if (videoFrame == NULL) {
ALOGE("getFrameAtTime: videoFrame is a NULL pointer");
@@ -423,7 +427,11 @@
VideoFrame *videoFrame = NULL;
sp<IMemory> frameMemory = retriever->getImageAtIndex(index, colorFormat);
if (frameMemory != 0) { // cast the shared structure to a VideoFrame object
- videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());
+ // TODO: Using unsecurePointer() has some associated security pitfalls
+ // (see declaration for details).
+ // Either document why it is safe in this case or address the
+ // issue (e.g. by copying).
+ videoFrame = static_cast<VideoFrame *>(frameMemory->unsecurePointer());
}
if (videoFrame == NULL) {
ALOGE("getImageAtIndex: videoFrame is a NULL pointer");
@@ -454,7 +462,11 @@
sp<IMemory> frameMemory = retriever->getImageAtIndex(
index, colorFormat, true /*metaOnly*/, true /*thumbnail*/);
if (frameMemory != 0) {
- videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());
+ // TODO: Using unsecurePointer() has some associated security pitfalls
+ // (see declaration for details).
+ // Either document why it is safe in this case or address the
+ // issue (e.g. by copying).
+ videoFrame = static_cast<VideoFrame *>(frameMemory->unsecurePointer());
int32_t thumbWidth = videoFrame->mWidth;
int32_t thumbHeight = videoFrame->mHeight;
videoFrame = NULL;
@@ -467,7 +479,11 @@
|| thumbPixels * 6 >= maxPixels) {
frameMemory = retriever->getImageAtIndex(
index, colorFormat, false /*metaOnly*/, true /*thumbnail*/);
- videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());
+ // TODO: Using unsecurePointer() has some associated security pitfalls
+ // (see declaration for details).
+ // Either document why it is safe in this case or address the
+ // issue (e.g. by copying).
+ videoFrame = static_cast<VideoFrame *>(frameMemory->unsecurePointer());
if (thumbPixels > maxPixels) {
int downscale = ceil(sqrt(thumbPixels / (float)maxPixels));
@@ -514,11 +530,15 @@
size_t i = 0;
for (; i < numFrames; i++) {
sp<IMemory> frame = retriever->getFrameAtIndex(frameIndex + i, colorFormat);
- if (frame == NULL || frame->pointer() == NULL) {
+ if (frame == NULL || frame->unsecurePointer() == NULL) {
ALOGE("video frame at index %zu is a NULL pointer", frameIndex + i);
break;
}
- VideoFrame *videoFrame = static_cast<VideoFrame *>(frame->pointer());
+ // TODO: Using unsecurePointer() has some associated security pitfalls
+ // (see declaration for details).
+ // Either document why it is safe in this case or address the
+ // issue (e.g. by copying).
+ VideoFrame *videoFrame = static_cast<VideoFrame *>(frame->unsecurePointer());
jobject bitmapObj = getBitmapFromVideoFrame(env, videoFrame, -1, -1, outColorType);
env->CallBooleanMethod(arrayList, fields.arrayListAdd, bitmapObj);
env->DeleteLocalRef(bitmapObj);
@@ -551,7 +571,11 @@
// the method name to getEmbeddedPicture().
sp<IMemory> albumArtMemory = retriever->extractAlbumArt();
if (albumArtMemory != 0) { // cast the shared structure to a MediaAlbumArt object
- mediaAlbumArt = static_cast<MediaAlbumArt *>(albumArtMemory->pointer());
+ // TODO: Using unsecurePointer() has some associated security pitfalls
+ // (see declaration for details).
+ // Either document why it is safe in this case or address the
+ // issue (e.g. by copying).
+ mediaAlbumArt = static_cast<MediaAlbumArt *>(albumArtMemory->unsecurePointer());
}
if (mediaAlbumArt == NULL) {
ALOGE("getEmbeddedPicture: Call to getEmbeddedPicture failed.");
diff --git a/media/jni/soundpool/SoundPool.h b/media/jni/soundpool/SoundPool.h
index 9d74103..01e4faa 100644
--- a/media/jni/soundpool/SoundPool.h
+++ b/media/jni/soundpool/SoundPool.h
@@ -61,7 +61,7 @@
audio_channel_mask_t channelMask() { return mChannelMask; }
size_t size() { return mSize; }
int state() { return mState; }
- uint8_t* data() { return static_cast<uint8_t*>(mData->pointer()); }
+ uint8_t* data() { return static_cast<uint8_t*>(mData->unsecurePointer()); }
status_t doLoad();
void startLoad() { mState = LOADING; }
sp<IMemory> getIMemory() { return mData; }
diff --git a/media/tests/audiotests/shared_mem_test.cpp b/media/tests/audiotests/shared_mem_test.cpp
index 2f57499..d586b6a 100644
--- a/media/tests/audiotests/shared_mem_test.cpp
+++ b/media/tests/audiotests/shared_mem_test.cpp
@@ -92,7 +92,7 @@
iMem = heap->allocate(BUF_SZ*sizeof(short));
- p = static_cast<uint8_t*>(iMem->pointer());
+ p = static_cast<uint8_t*>(iMem->unsecurePointer());
memcpy(p, smpBuf, BUF_SZ*sizeof(short));
sp<AudioTrack> track = new AudioTrack(AUDIO_STREAM_MUSIC,// stream type
diff --git a/core/proto/android/server/backup_chunks_metadata.proto b/packages/BackupEncryption/proto/backup_chunks_metadata.proto
similarity index 97%
rename from core/proto/android/server/backup_chunks_metadata.proto
rename to packages/BackupEncryption/proto/backup_chunks_metadata.proto
index a375f02..2fdedbf 100644
--- a/core/proto/android/server/backup_chunks_metadata.proto
+++ b/packages/BackupEncryption/proto/backup_chunks_metadata.proto
@@ -15,8 +15,10 @@
*/
syntax = "proto2";
-package com.android.server.backup.encryption.chunk;
+package android_backup_crypto;
+
+option java_package = "com.android.server.backup.encryption.protos";
option java_outer_classname = "ChunksMetadataProto";
// Cipher type with which the chunks are encrypted. For now we only support AES/GCM/NoPadding, but
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/CryptoSettings.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/CryptoSettings.java
index 2010620..033f1b1 100644
--- a/packages/BackupEncryption/src/com/android/server/backup/encryption/CryptoSettings.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/CryptoSettings.java
@@ -30,6 +30,7 @@
import java.security.KeyStoreException;
import java.util.Optional;
+import java.util.concurrent.TimeUnit;
/**
* State about encrypted backups that needs to be remembered.
@@ -51,6 +52,9 @@
SECONDARY_KEY_LAST_ROTATED_AT
};
+ private static final long DEFAULT_SECONDARY_KEY_ROTATION_PERIOD =
+ TimeUnit.MILLISECONDS.convert(31, TimeUnit.DAYS);
+
private static final String KEY_ANCESTRAL_SECONDARY_KEY_VERSION =
"ancestral_secondary_key_version";
@@ -202,6 +206,11 @@
.apply();
}
+ /** The number of milliseconds between secondary key rotation */
+ public long backupSecondaryKeyRotationIntervalMs() {
+ return DEFAULT_SECONDARY_KEY_ROTATION_PERIOD;
+ }
+
/** Deletes all crypto settings related to backup (as opposed to restore). */
public void clearAllSettingsForBackup() {
Editor sharedPrefsEditor = mSharedPreferences.edit();
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/Chunk.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/Chunk.java
deleted file mode 100644
index ba32860..0000000
--- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/Chunk.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2018 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.backup.encryption.chunk;
-
-import android.util.proto.ProtoInputStream;
-
-import java.io.IOException;
-
-/**
- * Information about a chunk entry in a protobuf. Only used for reading from a {@link
- * ProtoInputStream}.
- */
-public class Chunk {
- /**
- * Reads a Chunk from a {@link ProtoInputStream}. Expects the message to be of format {@link
- * ChunksMetadataProto.Chunk}.
- *
- * @param inputStream currently at a {@link ChunksMetadataProto.Chunk} message.
- * @throws IOException when the message is not structured as expected or a field can not be
- * read.
- */
- static Chunk readFromProto(ProtoInputStream inputStream) throws IOException {
- Chunk result = new Chunk();
-
- while (inputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
- switch (inputStream.getFieldNumber()) {
- case (int) ChunksMetadataProto.Chunk.HASH:
- result.mHash = inputStream.readBytes(ChunksMetadataProto.Chunk.HASH);
- break;
- case (int) ChunksMetadataProto.Chunk.LENGTH:
- result.mLength = inputStream.readInt(ChunksMetadataProto.Chunk.LENGTH);
- break;
- }
- }
-
- return result;
- }
-
- private int mLength;
- private byte[] mHash;
-
- /** Private constructor. This class should only be instantiated by calling readFromProto. */
- private Chunk() {
- // Set default values for fields in case they are not available in the proto.
- mHash = new byte[]{};
- mLength = 0;
- }
-
- public int getLength() {
- return mLength;
- }
-
- public byte[] getHash() {
- return mHash;
- }
-}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkListingMap.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkListingMap.java
deleted file mode 100644
index a448901..0000000
--- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkListingMap.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2019 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.backup.encryption.chunk;
-
-import android.annotation.Nullable;
-import android.util.proto.ProtoInputStream;
-
-import java.io.IOException;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Chunk listing in a format optimized for quick look-up of chunks via their hash keys. This is
- * useful when building an incremental backup. After a chunk has been produced, the algorithm can
- * quickly look up whether the chunk existed in the previous backup by checking this chunk listing.
- * It can then tell the server to use that chunk, through telling it the position and length of the
- * chunk in the previous backup's blob.
- */
-public class ChunkListingMap {
- /**
- * Reads a ChunkListingMap from a {@link ProtoInputStream}. Expects the message to be of format
- * {@link ChunksMetadataProto.ChunkListing}.
- *
- * @param inputStream Currently at a {@link ChunksMetadataProto.ChunkListing} message.
- * @throws IOException when the message is not structured as expected or a field can not be
- * read.
- */
- public static ChunkListingMap readFromProto(ProtoInputStream inputStream) throws IOException {
- Map<ChunkHash, Entry> entries = new HashMap();
-
- long start = 0;
-
- while (inputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
- if (inputStream.getFieldNumber() == (int) ChunksMetadataProto.ChunkListing.CHUNKS) {
- long chunkToken = inputStream.start(ChunksMetadataProto.ChunkListing.CHUNKS);
- Chunk chunk = Chunk.readFromProto(inputStream);
- entries.put(new ChunkHash(chunk.getHash()), new Entry(start, chunk.getLength()));
- start += chunk.getLength();
- inputStream.end(chunkToken);
- }
- }
-
- return new ChunkListingMap(entries);
- }
-
- private final Map<ChunkHash, Entry> mChunksByHash;
-
- private ChunkListingMap(Map<ChunkHash, Entry> chunksByHash) {
- mChunksByHash = Collections.unmodifiableMap(new HashMap<>(chunksByHash));
- }
-
- /** Returns {@code true} if there is a chunk with the given SHA-256 MAC key in the listing. */
- public boolean hasChunk(ChunkHash hash) {
- return mChunksByHash.containsKey(hash);
- }
-
- /**
- * Returns the entry for the chunk with the given hash.
- *
- * @param hash The SHA-256 MAC of the plaintext of the chunk.
- * @return The entry, containing position and length of the chunk in the backup blob, or null if
- * it does not exist.
- */
- @Nullable
- public Entry getChunkEntry(ChunkHash hash) {
- return mChunksByHash.get(hash);
- }
-
- /** Returns the number of chunks in this listing. */
- public int getChunkCount() {
- return mChunksByHash.size();
- }
-
- /** Information about a chunk entry in a backup blob - i.e., its position and length. */
- public static final class Entry {
- private final int mLength;
- private final long mStart;
-
- private Entry(long start, int length) {
- mStart = start;
- mLength = length;
- }
-
- /** Returns the length of the chunk in bytes. */
- public int getLength() {
- return mLength;
- }
-
- /** Returns the start position of the chunk in the backup blob, in bytes. */
- public long getStart() {
- return mStart;
- }
- }
-}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkOrderingType.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkOrderingType.java
index 8cb028e..9cda339 100644
--- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkOrderingType.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkOrderingType.java
@@ -16,9 +16,9 @@
package com.android.server.backup.encryption.chunk;
-import static com.android.server.backup.encryption.chunk.ChunksMetadataProto.CHUNK_ORDERING_TYPE_UNSPECIFIED;
-import static com.android.server.backup.encryption.chunk.ChunksMetadataProto.EXPLICIT_STARTS;
-import static com.android.server.backup.encryption.chunk.ChunksMetadataProto.INLINE_LENGTHS;
+import static com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.CHUNK_ORDERING_TYPE_UNSPECIFIED;
+import static com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.EXPLICIT_STARTS;
+import static com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.INLINE_LENGTHS;
import android.annotation.IntDef;
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoder.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoder.java
index 7b38dd4..6b9be9f 100644
--- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoder.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoder.java
@@ -17,7 +17,7 @@
package com.android.server.backup.encryption.chunking;
import com.android.server.backup.encryption.chunk.ChunkOrderingType;
-import com.android.server.backup.encryption.chunk.ChunksMetadataProto;
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto;
import java.io.IOException;
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoder.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoder.java
index 567f75d..e707350 100644
--- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoder.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoder.java
@@ -17,7 +17,7 @@
package com.android.server.backup.encryption.chunking;
import com.android.server.backup.encryption.chunk.ChunkOrderingType;
-import com.android.server.backup.encryption.chunk.ChunksMetadataProto;
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto;
import java.io.IOException;
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/SecondaryKeyRotationScheduler.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/SecondaryKeyRotationScheduler.java
new file mode 100644
index 0000000..91b57cf
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/SecondaryKeyRotationScheduler.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2019 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.backup.encryption.keys;
+
+import android.content.Context;
+import android.util.Slog;
+
+import com.android.server.backup.encryption.CryptoSettings;
+import com.android.server.backup.encryption.tasks.StartSecondaryKeyRotationTask;
+
+import java.io.File;
+import java.time.Clock;
+import java.util.Optional;
+
+/**
+ * Helps schedule rotations of secondary keys.
+ *
+ * <p>TODO(b/72028016) Replace with a job.
+ */
+public class SecondaryKeyRotationScheduler {
+
+ private static final String TAG = "SecondaryKeyRotationScheduler";
+ private static final String SENTINEL_FILE_PATH = "force_secondary_key_rotation";
+
+ private final Context mContext;
+ private final RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager;
+ private final CryptoSettings mCryptoSettings;
+ private final Clock mClock;
+
+ public SecondaryKeyRotationScheduler(
+ Context context,
+ RecoverableKeyStoreSecondaryKeyManager secondaryKeyManager,
+ CryptoSettings cryptoSettings,
+ Clock clock) {
+ mContext = context;
+ mCryptoSettings = cryptoSettings;
+ mClock = clock;
+ mSecondaryKeyManager = secondaryKeyManager;
+ }
+
+ /**
+ * Returns {@code true} if a sentinel file for forcing secondary key rotation is present. This
+ * is only for testing purposes.
+ */
+ private boolean isForceRotationTestSentinelPresent() {
+ File file = new File(mContext.getFilesDir(), SENTINEL_FILE_PATH);
+ if (file.exists()) {
+ file.delete();
+ return true;
+ }
+ return false;
+ }
+
+ /** Start the key rotation task if it's time to do so */
+ public void startRotationIfScheduled() {
+ if (isForceRotationTestSentinelPresent()) {
+ Slog.i(TAG, "Found force flag for secondary rotation. Starting now.");
+ startRotation();
+ return;
+ }
+
+ Optional<Long> maybeLastRotated = mCryptoSettings.getSecondaryLastRotated();
+ if (!maybeLastRotated.isPresent()) {
+ Slog.v(TAG, "No previous rotation, scheduling from now.");
+ scheduleRotationFromNow();
+ return;
+ }
+
+ long lastRotated = maybeLastRotated.get();
+ long now = mClock.millis();
+
+ if (lastRotated > now) {
+ Slog.i(TAG, "Last rotation was in the future. Clock must have changed. Rotate now.");
+ startRotation();
+ return;
+ }
+
+ long millisSinceLastRotation = now - lastRotated;
+ long rotationInterval = mCryptoSettings.backupSecondaryKeyRotationIntervalMs();
+ if (millisSinceLastRotation >= rotationInterval) {
+ Slog.i(
+ TAG,
+ "Last rotation was more than "
+ + rotationInterval
+ + "ms ("
+ + millisSinceLastRotation
+ + "ms) in the past. Rotate now.");
+ startRotation();
+ }
+
+ Slog.v(TAG, "No rotation required, last " + lastRotated + ".");
+ }
+
+ private void startRotation() {
+ scheduleRotationFromNow();
+ new StartSecondaryKeyRotationTask(mCryptoSettings, mSecondaryKeyManager).run();
+ }
+
+ private void scheduleRotationFromNow() {
+ mCryptoSettings.setSecondaryLastRotated(mClock.millis());
+ }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/StartSecondaryKeyRotationTask.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/StartSecondaryKeyRotationTask.java
new file mode 100644
index 0000000..77cfded
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/StartSecondaryKeyRotationTask.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2019 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.backup.encryption.tasks;
+
+import android.security.keystore.recovery.InternalRecoveryServiceException;
+import android.security.keystore.recovery.LockScreenRequiredException;
+import android.util.Slog;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.backup.encryption.CryptoSettings;
+import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey;
+import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKeyManager;
+
+import java.security.UnrecoverableKeyException;
+import java.util.Optional;
+
+/**
+ * Starts rotating to a new secondary key. Cannot complete until the screen is unlocked and the new
+ * key is synced.
+ */
+public class StartSecondaryKeyRotationTask {
+ private static final String TAG = "BE-StSecondaryKeyRotTsk";
+
+ private final CryptoSettings mCryptoSettings;
+ private final RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager;
+
+ public StartSecondaryKeyRotationTask(
+ CryptoSettings cryptoSettings,
+ RecoverableKeyStoreSecondaryKeyManager secondaryKeyManager) {
+ mCryptoSettings = Preconditions.checkNotNull(cryptoSettings);
+ mSecondaryKeyManager = Preconditions.checkNotNull(secondaryKeyManager);
+ }
+
+ /** Begin the key rotation */
+ public void run() {
+ Slog.i(TAG, "Attempting to initiate a secondary key rotation.");
+
+ Optional<String> maybeCurrentAlias = mCryptoSettings.getActiveSecondaryKeyAlias();
+ if (!maybeCurrentAlias.isPresent()) {
+ Slog.w(TAG, "No active current alias. Cannot trigger a secondary rotation.");
+ return;
+ }
+ String currentAlias = maybeCurrentAlias.get();
+
+ Optional<String> maybeNextAlias = mCryptoSettings.getNextSecondaryKeyAlias();
+ if (maybeNextAlias.isPresent()) {
+ String nextAlias = maybeNextAlias.get();
+ if (nextAlias.equals(currentAlias)) {
+ // Shouldn't be possible, but guard against accidentally deleting the active key.
+ Slog.e(TAG, "Was already trying to rotate to what is already the active key.");
+ } else {
+ Slog.w(TAG, "Was already rotating to another key. Cancelling that.");
+ try {
+ mSecondaryKeyManager.remove(nextAlias);
+ } catch (Exception e) {
+ Slog.wtf(TAG, "Could not remove old key", e);
+ }
+ }
+ mCryptoSettings.removeNextSecondaryKeyAlias();
+ }
+
+ RecoverableKeyStoreSecondaryKey newSecondaryKey;
+ try {
+ newSecondaryKey = mSecondaryKeyManager.generate();
+ } catch (LockScreenRequiredException e) {
+ Slog.e(TAG, "No lock screen is set - cannot generate a new key to rotate to.", e);
+ return;
+ } catch (InternalRecoveryServiceException e) {
+ Slog.e(TAG, "Internal error in Recovery Controller, failed to rotate key.", e);
+ return;
+ } catch (UnrecoverableKeyException e) {
+ Slog.e(TAG, "Failed to get key after generating, failed to rotate", e);
+ return;
+ }
+
+ String alias = newSecondaryKey.getAlias();
+ Slog.i(TAG, "Generated a new secondary key with alias '" + alias + "'.");
+ try {
+ mCryptoSettings.setNextSecondaryAlias(alias);
+ Slog.i(TAG, "Successfully set '" + alias + "' as next key to rotate to");
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, "Unexpected error setting next alias", e);
+ try {
+ mSecondaryKeyManager.remove(alias);
+ } catch (Exception err) {
+ Slog.wtf(TAG, "Failed to remove generated key after encountering error", err);
+ }
+ }
+ }
+}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/ChunkListingMapTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/ChunkListingMapTest.java
deleted file mode 100644
index 24e5573..0000000
--- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/ChunkListingMapTest.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright (C) 2019 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.backup.encryption.chunk;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.platform.test.annotations.Presubmit;
-import android.util.proto.ProtoInputStream;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.util.Preconditions;
-
-import com.google.common.base.Charsets;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-
-import java.io.ByteArrayInputStream;
-import java.util.Arrays;
-
-@RunWith(RobolectricTestRunner.class)
-@Presubmit
-public class ChunkListingMapTest {
- private static final String CHUNK_A = "CHUNK_A";
- private static final String CHUNK_B = "CHUNK_B";
- private static final String CHUNK_C = "CHUNK_C";
-
- private static final int CHUNK_A_LENGTH = 256;
- private static final int CHUNK_B_LENGTH = 1024;
- private static final int CHUNK_C_LENGTH = 4055;
-
- private ChunkHash mChunkHashA;
- private ChunkHash mChunkHashB;
- private ChunkHash mChunkHashC;
-
- @Before
- public void setUp() throws Exception {
- mChunkHashA = getHash(CHUNK_A);
- mChunkHashB = getHash(CHUNK_B);
- mChunkHashC = getHash(CHUNK_C);
- }
-
- @Test
- public void testHasChunk_whenChunkInListing_returnsTrue() throws Exception {
- byte[] chunkListingProto =
- createChunkListingProto(
- new ChunkHash[] {mChunkHashA, mChunkHashB, mChunkHashC},
- new int[] {CHUNK_A_LENGTH, CHUNK_B_LENGTH, CHUNK_C_LENGTH});
- ChunkListingMap chunkListingMap =
- ChunkListingMap.readFromProto(
- new ProtoInputStream(new ByteArrayInputStream(chunkListingProto)));
-
- boolean chunkAInList = chunkListingMap.hasChunk(mChunkHashA);
- boolean chunkBInList = chunkListingMap.hasChunk(mChunkHashB);
- boolean chunkCInList = chunkListingMap.hasChunk(mChunkHashC);
-
- assertThat(chunkAInList).isTrue();
- assertThat(chunkBInList).isTrue();
- assertThat(chunkCInList).isTrue();
- }
-
- @Test
- public void testHasChunk_whenChunkNotInListing_returnsFalse() throws Exception {
- byte[] chunkListingProto =
- createChunkListingProto(
- new ChunkHash[] {mChunkHashA, mChunkHashB},
- new int[] {CHUNK_A_LENGTH, CHUNK_B_LENGTH});
- ChunkListingMap chunkListingMap =
- ChunkListingMap.readFromProto(
- new ProtoInputStream(new ByteArrayInputStream(chunkListingProto)));
- ChunkHash chunkHashEmpty = getHash("");
-
- boolean chunkCInList = chunkListingMap.hasChunk(mChunkHashC);
- boolean emptyChunkInList = chunkListingMap.hasChunk(chunkHashEmpty);
-
- assertThat(chunkCInList).isFalse();
- assertThat(emptyChunkInList).isFalse();
- }
-
- @Test
- public void testGetChunkEntry_returnsEntryWithCorrectLength() throws Exception {
- byte[] chunkListingProto =
- createChunkListingProto(
- new ChunkHash[] {mChunkHashA, mChunkHashB, mChunkHashC},
- new int[] {CHUNK_A_LENGTH, CHUNK_B_LENGTH, CHUNK_C_LENGTH});
- ChunkListingMap chunkListingMap =
- ChunkListingMap.readFromProto(
- new ProtoInputStream(new ByteArrayInputStream(chunkListingProto)));
-
- ChunkListingMap.Entry entryA = chunkListingMap.getChunkEntry(mChunkHashA);
- ChunkListingMap.Entry entryB = chunkListingMap.getChunkEntry(mChunkHashB);
- ChunkListingMap.Entry entryC = chunkListingMap.getChunkEntry(mChunkHashC);
-
- assertThat(entryA.getLength()).isEqualTo(CHUNK_A_LENGTH);
- assertThat(entryB.getLength()).isEqualTo(CHUNK_B_LENGTH);
- assertThat(entryC.getLength()).isEqualTo(CHUNK_C_LENGTH);
- }
-
- @Test
- public void testGetChunkEntry_returnsEntryWithCorrectStart() throws Exception {
- byte[] chunkListingProto =
- createChunkListingProto(
- new ChunkHash[] {mChunkHashA, mChunkHashB, mChunkHashC},
- new int[] {CHUNK_A_LENGTH, CHUNK_B_LENGTH, CHUNK_C_LENGTH});
- ChunkListingMap chunkListingMap =
- ChunkListingMap.readFromProto(
- new ProtoInputStream(new ByteArrayInputStream(chunkListingProto)));
-
- ChunkListingMap.Entry entryA = chunkListingMap.getChunkEntry(mChunkHashA);
- ChunkListingMap.Entry entryB = chunkListingMap.getChunkEntry(mChunkHashB);
- ChunkListingMap.Entry entryC = chunkListingMap.getChunkEntry(mChunkHashC);
-
- assertThat(entryA.getStart()).isEqualTo(0);
- assertThat(entryB.getStart()).isEqualTo(CHUNK_A_LENGTH);
- assertThat(entryC.getStart()).isEqualTo(CHUNK_A_LENGTH + CHUNK_B_LENGTH);
- }
-
- @Test
- public void testGetChunkEntry_returnsNullForNonExistentChunk() throws Exception {
- byte[] chunkListingProto =
- createChunkListingProto(
- new ChunkHash[] {mChunkHashA, mChunkHashB},
- new int[] {CHUNK_A_LENGTH, CHUNK_B_LENGTH});
- ChunkListingMap chunkListingMap =
- ChunkListingMap.readFromProto(
- new ProtoInputStream(new ByteArrayInputStream(chunkListingProto)));
-
- ChunkListingMap.Entry chunkEntryNonexistentChunk =
- chunkListingMap.getChunkEntry(mChunkHashC);
-
- assertThat(chunkEntryNonexistentChunk).isNull();
- }
-
- @Test
- public void testReadFromProto_whenEmptyProto_returnsChunkListingMapWith0Chunks()
- throws Exception {
- ProtoInputStream emptyProto = new ProtoInputStream(new ByteArrayInputStream(new byte[] {}));
-
- ChunkListingMap chunkListingMap = ChunkListingMap.readFromProto(emptyProto);
-
- assertThat(chunkListingMap.getChunkCount()).isEqualTo(0);
- }
-
- @Test
- public void testReadFromProto_returnsChunkListingWithCorrectSize() throws Exception {
- byte[] chunkListingProto =
- createChunkListingProto(
- new ChunkHash[] {mChunkHashA, mChunkHashB, mChunkHashC},
- new int[] {CHUNK_A_LENGTH, CHUNK_B_LENGTH, CHUNK_C_LENGTH});
-
- ChunkListingMap chunkListingMap =
- ChunkListingMap.readFromProto(
- new ProtoInputStream(new ByteArrayInputStream(chunkListingProto)));
-
- assertThat(chunkListingMap.getChunkCount()).isEqualTo(3);
- }
-
- private byte[] createChunkListingProto(ChunkHash[] hashes, int[] lengths) {
- Preconditions.checkArgument(hashes.length == lengths.length);
- ProtoOutputStream outputStream = new ProtoOutputStream();
-
- for (int i = 0; i < hashes.length; ++i) {
- writeToProtoOutputStream(outputStream, hashes[i], lengths[i]);
- }
- outputStream.flush();
-
- return outputStream.getBytes();
- }
-
- private void writeToProtoOutputStream(ProtoOutputStream out, ChunkHash chunkHash, int length) {
- long token = out.start(ChunksMetadataProto.ChunkListing.CHUNKS);
- out.write(ChunksMetadataProto.Chunk.HASH, chunkHash.getHash());
- out.write(ChunksMetadataProto.Chunk.LENGTH, length);
- out.end(token);
- }
-
- private ChunkHash getHash(String name) {
- return new ChunkHash(
- Arrays.copyOf(name.getBytes(Charsets.UTF_8), ChunkHash.HASH_LENGTH_BYTES));
- }
-}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/ChunkTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/ChunkTest.java
deleted file mode 100644
index 1796f56..0000000
--- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/ChunkTest.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2018 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.backup.encryption.chunk;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.platform.test.annotations.Presubmit;
-import android.util.proto.ProtoInputStream;
-import android.util.proto.ProtoOutputStream;
-
-import com.google.common.base.Charsets;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-
-import java.io.ByteArrayInputStream;
-import java.util.Arrays;
-
-@RunWith(RobolectricTestRunner.class)
-@Presubmit
-public class ChunkTest {
- private static final String CHUNK_A = "CHUNK_A";
- private static final int CHUNK_A_LENGTH = 256;
-
- private ChunkHash mChunkHashA;
-
- @Before
- public void setUp() throws Exception {
- mChunkHashA = getHash(CHUNK_A);
- }
-
- @Test
- public void testReadFromProto_readsCorrectly() throws Exception {
- ProtoOutputStream out = new ProtoOutputStream();
- out.write(ChunksMetadataProto.Chunk.HASH, mChunkHashA.getHash());
- out.write(ChunksMetadataProto.Chunk.LENGTH, CHUNK_A_LENGTH);
- out.flush();
- byte[] protoBytes = out.getBytes();
-
- Chunk chunk =
- Chunk.readFromProto(new ProtoInputStream(new ByteArrayInputStream(protoBytes)));
-
- assertThat(chunk.getHash()).isEqualTo(mChunkHashA.getHash());
- assertThat(chunk.getLength()).isEqualTo(CHUNK_A_LENGTH);
- }
-
- @Test
- public void testReadFromProto_whenFieldsWrittenInReversedOrder_readsCorrectly()
- throws Exception {
- ProtoOutputStream out = new ProtoOutputStream();
- // Write fields of Chunk proto in reverse order.
- out.write(ChunksMetadataProto.Chunk.LENGTH, CHUNK_A_LENGTH);
- out.write(ChunksMetadataProto.Chunk.HASH, mChunkHashA.getHash());
- out.flush();
- byte[] protoBytes = out.getBytes();
-
- Chunk chunk =
- Chunk.readFromProto(new ProtoInputStream(new ByteArrayInputStream(protoBytes)));
-
- assertThat(chunk.getHash()).isEqualTo(mChunkHashA.getHash());
- assertThat(chunk.getLength()).isEqualTo(CHUNK_A_LENGTH);
- }
-
- @Test
- public void testReadFromProto_whenEmptyProto_returnsEmptyHash() throws Exception {
- ProtoInputStream emptyProto = new ProtoInputStream(new ByteArrayInputStream(new byte[] {}));
-
- Chunk chunk = Chunk.readFromProto(emptyProto);
-
- assertThat(chunk.getHash()).asList().hasSize(0);
- assertThat(chunk.getLength()).isEqualTo(0);
- }
-
- @Test
- public void testReadFromProto_whenOnlyHashSet_returnsChunkWithOnlyHash() throws Exception {
- ProtoOutputStream out = new ProtoOutputStream();
- out.write(ChunksMetadataProto.Chunk.HASH, mChunkHashA.getHash());
- out.flush();
- byte[] protoBytes = out.getBytes();
-
- Chunk chunk =
- Chunk.readFromProto(new ProtoInputStream(new ByteArrayInputStream(protoBytes)));
-
- assertThat(chunk.getHash()).isEqualTo(mChunkHashA.getHash());
- assertThat(chunk.getLength()).isEqualTo(0);
- }
-
- @Test
- public void testReadFromProto_whenOnlyLengthSet_returnsChunkWithOnlyLength() throws Exception {
- ProtoOutputStream out = new ProtoOutputStream();
- out.write(ChunksMetadataProto.Chunk.LENGTH, CHUNK_A_LENGTH);
- out.flush();
- byte[] protoBytes = out.getBytes();
-
- Chunk chunk =
- Chunk.readFromProto(new ProtoInputStream(new ByteArrayInputStream(protoBytes)));
-
- assertThat(chunk.getHash()).isEqualTo(new byte[] {});
- assertThat(chunk.getLength()).isEqualTo(CHUNK_A_LENGTH);
- }
-
- private ChunkHash getHash(String name) {
- return new ChunkHash(
- Arrays.copyOf(name.getBytes(Charsets.UTF_8), ChunkHash.HASH_LENGTH_BYTES));
- }
-}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoderTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoderTest.java
index 634acdc..7e1fded 100644
--- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoderTest.java
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoderTest.java
@@ -24,7 +24,7 @@
import android.platform.test.annotations.Presubmit;
import com.android.server.backup.encryption.chunk.ChunkHash;
-import com.android.server.backup.encryption.chunk.ChunksMetadataProto;
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto;
import org.junit.Before;
import org.junit.Test;
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoderTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoderTest.java
index d231603..6f58ee1 100644
--- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoderTest.java
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoderTest.java
@@ -24,7 +24,7 @@
import android.platform.test.annotations.Presubmit;
import com.android.server.backup.encryption.chunk.ChunkHash;
-import com.android.server.backup.encryption.chunk.ChunksMetadataProto;
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto;
import org.junit.Before;
import org.junit.Test;
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/SecondaryKeyRotationSchedulerTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/SecondaryKeyRotationSchedulerTest.java
new file mode 100644
index 0000000..c31d19d
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/SecondaryKeyRotationSchedulerTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2019 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.backup.encryption.keys;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.server.backup.encryption.CryptoSettings;
+import com.android.server.backup.encryption.tasks.StartSecondaryKeyRotationTask;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+import java.io.File;
+import java.time.Clock;
+
+@Config(shadows = SecondaryKeyRotationSchedulerTest.ShadowStartSecondaryKeyRotationTask.class)
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class SecondaryKeyRotationSchedulerTest {
+ private static final String SENTINEL_FILE_PATH = "force_secondary_key_rotation";
+
+ @Mock private RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager;
+ @Mock private Clock mClock;
+
+ private CryptoSettings mCryptoSettings;
+ private SecondaryKeyRotationScheduler mScheduler;
+ private long mRotationIntervalMillis;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ Context application = ApplicationProvider.getApplicationContext();
+
+ mCryptoSettings = CryptoSettings.getInstanceForTesting(application);
+ mRotationIntervalMillis = mCryptoSettings.backupSecondaryKeyRotationIntervalMs();
+
+ mScheduler =
+ new SecondaryKeyRotationScheduler(
+ application, mSecondaryKeyManager, mCryptoSettings, mClock);
+ ShadowStartSecondaryKeyRotationTask.reset();
+ }
+
+ @Test
+ public void startRotationIfScheduled_rotatesIfRotationWasFarEnoughInThePast() {
+ long lastRotated = 100009;
+ mCryptoSettings.setSecondaryLastRotated(lastRotated);
+ setNow(lastRotated + mRotationIntervalMillis);
+
+ mScheduler.startRotationIfScheduled();
+
+ assertThat(ShadowStartSecondaryKeyRotationTask.sRan).isTrue();
+ }
+
+ @Test
+ public void startRotationIfScheduled_setsNewRotationTimeIfRotationWasFarEnoughInThePast() {
+ long lastRotated = 100009;
+ long now = lastRotated + mRotationIntervalMillis;
+ mCryptoSettings.setSecondaryLastRotated(lastRotated);
+ setNow(now);
+
+ mScheduler.startRotationIfScheduled();
+
+ assertThat(mCryptoSettings.getSecondaryLastRotated().get()).isEqualTo(now);
+ }
+
+ @Test
+ public void startRotationIfScheduled_rotatesIfClockHasChanged() {
+ long lastRotated = 100009;
+ mCryptoSettings.setSecondaryLastRotated(lastRotated);
+ setNow(lastRotated - 1);
+
+ mScheduler.startRotationIfScheduled();
+
+ assertThat(ShadowStartSecondaryKeyRotationTask.sRan).isTrue();
+ }
+
+ @Test
+ public void startRotationIfScheduled_rotatesIfSentinelFileIsPresent() throws Exception {
+ File file = new File(RuntimeEnvironment.application.getFilesDir(), SENTINEL_FILE_PATH);
+ file.createNewFile();
+
+ mScheduler.startRotationIfScheduled();
+
+ assertThat(ShadowStartSecondaryKeyRotationTask.sRan).isTrue();
+ }
+
+ @Test
+ public void startRotationIfScheduled_setsNextRotationIfClockHasChanged() {
+ long lastRotated = 100009;
+ long now = lastRotated - 1;
+ mCryptoSettings.setSecondaryLastRotated(lastRotated);
+ setNow(now);
+
+ mScheduler.startRotationIfScheduled();
+
+ assertThat(mCryptoSettings.getSecondaryLastRotated().get()).isEqualTo(now);
+ }
+
+ @Test
+ public void startRotationIfScheduled_doesNothingIfRotationWasRecentEnough() {
+ long lastRotated = 100009;
+ mCryptoSettings.setSecondaryLastRotated(lastRotated);
+ setNow(lastRotated + mRotationIntervalMillis - 1);
+
+ mScheduler.startRotationIfScheduled();
+
+ assertThat(ShadowStartSecondaryKeyRotationTask.sRan).isFalse();
+ }
+
+ @Test
+ public void startRotationIfScheduled_doesNotSetRotationTimeIfRotationWasRecentEnough() {
+ long lastRotated = 100009;
+ mCryptoSettings.setSecondaryLastRotated(lastRotated);
+ setNow(lastRotated + mRotationIntervalMillis - 1);
+
+ mScheduler.startRotationIfScheduled();
+
+ assertThat(mCryptoSettings.getSecondaryLastRotated().get()).isEqualTo(lastRotated);
+ }
+
+ @Test
+ public void startRotationIfScheduled_setsLastRotatedToNowIfNeverRotated() {
+ long now = 13295436;
+ setNow(now);
+
+ mScheduler.startRotationIfScheduled();
+
+ assertThat(mCryptoSettings.getSecondaryLastRotated().get()).isEqualTo(now);
+ }
+
+ private void setNow(long timestamp) {
+ when(mClock.millis()).thenReturn(timestamp);
+ }
+
+ @Implements(StartSecondaryKeyRotationTask.class)
+ public static class ShadowStartSecondaryKeyRotationTask {
+ private static boolean sRan = false;
+
+ @Implementation
+ public void run() {
+ sRan = true;
+ }
+
+ @Resetter
+ public static void reset() {
+ sRan = false;
+ }
+ }
+}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/StartSecondaryKeyRotationTaskTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/StartSecondaryKeyRotationTaskTest.java
new file mode 100644
index 0000000..4ac4fa8
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/StartSecondaryKeyRotationTaskTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2019 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.backup.encryption.tasks;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+import android.security.keystore.recovery.RecoveryController;
+
+import com.android.server.backup.encryption.CryptoSettings;
+import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey;
+import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKeyManager;
+import com.android.server.testing.shadows.ShadowRecoveryController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.security.SecureRandom;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowRecoveryController.class})
+@Presubmit
+public class StartSecondaryKeyRotationTaskTest {
+
+ private CryptoSettings mCryptoSettings;
+ private RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager;
+ private StartSecondaryKeyRotationTask mStartSecondaryKeyRotationTask;
+
+ @Before
+ public void setUp() throws Exception {
+ mSecondaryKeyManager =
+ new RecoverableKeyStoreSecondaryKeyManager(
+ RecoveryController.getInstance(RuntimeEnvironment.application),
+ new SecureRandom());
+ mCryptoSettings = CryptoSettings.getInstanceForTesting(RuntimeEnvironment.application);
+ mStartSecondaryKeyRotationTask =
+ new StartSecondaryKeyRotationTask(mCryptoSettings, mSecondaryKeyManager);
+
+ ShadowRecoveryController.reset();
+ }
+
+ @Test
+ public void run_doesNothingIfNoActiveSecondaryExists() {
+ mStartSecondaryKeyRotationTask.run();
+
+ assertThat(mCryptoSettings.getNextSecondaryKeyAlias().isPresent()).isFalse();
+ }
+
+ @Test
+ public void run_doesNotRemoveExistingNextSecondaryKeyIfItIsAlreadyActive() throws Exception {
+ generateAnActiveKey();
+ String activeAlias = mCryptoSettings.getActiveSecondaryKeyAlias().get();
+ mCryptoSettings.setNextSecondaryAlias(activeAlias);
+
+ mStartSecondaryKeyRotationTask.run();
+
+ assertThat(mSecondaryKeyManager.get(activeAlias).isPresent()).isTrue();
+ }
+
+ @Test
+ public void run_doesRemoveExistingNextSecondaryKeyIfItIsNotYetActive() throws Exception {
+ generateAnActiveKey();
+ RecoverableKeyStoreSecondaryKey nextKey = mSecondaryKeyManager.generate();
+ String nextAlias = nextKey.getAlias();
+ mCryptoSettings.setNextSecondaryAlias(nextAlias);
+
+ mStartSecondaryKeyRotationTask.run();
+
+ assertThat(mSecondaryKeyManager.get(nextAlias).isPresent()).isFalse();
+ }
+
+ @Test
+ public void run_generatesANewNextSecondaryKey() throws Exception {
+ generateAnActiveKey();
+
+ mStartSecondaryKeyRotationTask.run();
+
+ assertThat(mCryptoSettings.getNextSecondaryKeyAlias().isPresent()).isTrue();
+ }
+
+ @Test
+ public void run_generatesANewKeyThatExistsInKeyStore() throws Exception {
+ generateAnActiveKey();
+
+ mStartSecondaryKeyRotationTask.run();
+
+ String nextAlias = mCryptoSettings.getNextSecondaryKeyAlias().get();
+ assertThat(mSecondaryKeyManager.get(nextAlias).isPresent()).isTrue();
+ }
+
+ private void generateAnActiveKey() throws Exception {
+ RecoverableKeyStoreSecondaryKey secondaryKey = mSecondaryKeyManager.generate();
+ mCryptoSettings.setActiveSecondaryKeyAlias(secondaryKey.getAlias());
+ }
+}
diff --git a/packages/CarrierDefaultApp/tests/unit/Android.bp b/packages/CarrierDefaultApp/tests/unit/Android.bp
index 96144cf..5655abb 100644
--- a/packages/CarrierDefaultApp/tests/unit/Android.bp
+++ b/packages/CarrierDefaultApp/tests/unit/Android.bp
@@ -17,7 +17,6 @@
certificate: "platform",
libs: [
"android.test.runner",
- "telephony-common",
"android.test.base",
],
static_libs: [
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 32fc7ff..720266a 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -1521,14 +1521,14 @@
return false;
}
- // Special cases for location providers (sigh).
- if (Settings.Secure.LOCATION_PROVIDERS_ALLOWED.equals(name)) {
- return updateLocationProvidersAllowedLocked(value, tag, owningUserId, makeDefault,
- forceNotify);
- }
-
// Mutate the value.
synchronized (mLock) {
+ // Special cases for location providers (sigh).
+ if (Settings.Secure.LOCATION_PROVIDERS_ALLOWED.equals(name)) {
+ return updateLocationProvidersAllowedLocked(value, tag, owningUserId, makeDefault,
+ forceNotify);
+ }
+
switch (operation) {
case MUTATION_OPERATION_INSERT: {
return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SECURE,
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 8c0108d..602fe3e 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -1264,9 +1264,7 @@
}
return new Notification.Builder(context, NOTIFICATION_CHANNEL_ID)
.addExtras(sNotificationBundle)
- .setSmallIcon(
- isTv(context) ? R.drawable.ic_bug_report_black_24dp
- : com.android.internal.R.drawable.stat_sys_adb)
+ .setSmallIcon(R.drawable.ic_bug_report_black_24dp)
.setLocalOnly(true)
.setColor(context.getColor(
com.android.internal.R.color.system_notification_accent_color))
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/SensorManagerPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/SensorManagerPlugin.java
index 60435d0..d62c1d4 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/SensorManagerPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/SensorManagerPlugin.java
@@ -22,7 +22,7 @@
/**
* Allows for additional sensors to be retrieved from
- * {@link com.android.systemui.util.AsyncSensorManager}.
+ * {@link com.android.systemui.util.sensors.AsyncSensorManager}.
*/
@ProvidesInterface(action = SensorManagerPlugin.ACTION, version = SensorManagerPlugin.VERSION)
public interface SensorManagerPlugin extends Plugin {
diff --git a/packages/SystemUI/res/drawable/biometric_dialog_bg.xml b/packages/SystemUI/res/drawable/biometric_dialog_bg.xml
deleted file mode 100644
index 0c6d57d..0000000
--- a/packages/SystemUI/res/drawable/biometric_dialog_bg.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
- ~ Copyright (C) 2018 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
- -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="?android:attr/colorBackgroundFloating" />
- <corners
- android:topLeftRadius="@dimen/biometric_dialog_corner_size"
- android:topRightRadius="@dimen/biometric_dialog_corner_size"
- android:bottomLeftRadius="@dimen/biometric_dialog_corner_size"
- android:bottomRightRadius="@dimen/biometric_dialog_corner_size"/>
-</shape>
diff --git a/packages/SystemUI/res/layout/auth_biometric_contents.xml b/packages/SystemUI/res/layout/auth_biometric_contents.xml
index 925e4fa..a1006a8 100644
--- a/packages/SystemUI/res/layout/auth_biometric_contents.xml
+++ b/packages/SystemUI/res/layout/auth_biometric_contents.xml
@@ -42,7 +42,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="24dp"
- android:paddingBottom="48dp"
android:paddingTop="8dp"
android:gravity="@integer/biometric_dialog_text_gravity"
android:textSize="16sp"
@@ -52,6 +51,7 @@
android:id="@+id/biometric_icon"
android:layout_width="@dimen/biometric_dialog_biometric_icon_size"
android:layout_height="@dimen/biometric_dialog_biometric_icon_size"
+ android:paddingTop="48dp"
android:layout_gravity="center_horizontal"
android:scaleType="fitXY" />
diff --git a/packages/SystemUI/res/layout/biometric_dialog.xml b/packages/SystemUI/res/layout/biometric_dialog.xml
deleted file mode 100644
index e687cdf..0000000
--- a/packages/SystemUI/res/layout/biometric_dialog.xml
+++ /dev/null
@@ -1,190 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2018 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
- -->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/layout"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <ImageView
- android:id="@+id/background"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:scaleType="center" />
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:gravity="bottom"
- android:background="@color/biometric_dialog_dim_color"
- android:orientation="vertical">
-
- <!-- This is not a Space since Spaces cannot be clicked -->
- <View
- android:id="@+id/space"
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1"
- android:contentDescription="@string/biometric_dialog_empty_space_description"/>
-
- <ScrollView
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal">
-
- <!-- This is not a Space since Spaces cannot be clicked. The width of this changes
- depending on horizontal/portrait orientation -->
- <View
- android:id="@+id/left_space"
- android:layout_weight="1"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:contentDescription="@string/biometric_dialog_empty_space_description"/>
-
- <LinearLayout
- android:id="@+id/dialog"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:background="@drawable/biometric_dialog_bg"
- android:layout_marginBottom="@dimen/biometric_dialog_border_padding"
- android:layout_marginLeft="@dimen/biometric_dialog_border_padding"
- android:layout_marginRight="@dimen/biometric_dialog_border_padding">
-
- <TextView
- android:id="@+id/title"
- android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginEnd="24dp"
- android:layout_marginStart="24dp"
- android:layout_marginTop="24dp"
- android:gravity="@integer/biometric_dialog_text_gravity"
- android:textSize="20sp"
- android:textColor="?android:attr/textColorPrimary"/>
-
- <TextView
- android:id="@+id/subtitle"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="8dp"
- android:layout_marginStart="24dp"
- android:layout_marginEnd="24dp"
- android:gravity="@integer/biometric_dialog_text_gravity"
- android:textSize="16sp"
- android:textColor="?android:attr/textColorPrimary"/>
-
- <TextView
- android:id="@+id/description"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginEnd="24dp"
- android:layout_marginStart="24dp"
- android:gravity="@integer/biometric_dialog_text_gravity"
- android:paddingTop="8dp"
- android:textSize="16sp"
- android:textColor="?android:attr/textColorPrimary"/>
-
- <ImageView
- android:id="@+id/biometric_icon"
- android:layout_width="@dimen/biometric_dialog_biometric_icon_size"
- android:layout_height="@dimen/biometric_dialog_biometric_icon_size"
- android:layout_gravity="center_horizontal"
- android:layout_marginTop="48dp"
- android:scaleType="fitXY" />
-
- <TextView
- android:id="@+id/error"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginEnd="24dp"
- android:layout_marginStart="24dp"
- android:paddingTop="16dp"
- android:paddingBottom="24dp"
- android:textSize="12sp"
- android:gravity="center_horizontal"
- android:accessibilityLiveRegion="polite"
- android:textColor="@color/biometric_dialog_gray"/>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="72dip"
- android:paddingTop="24dp"
- android:layout_gravity="center_vertical"
- style="?android:attr/buttonBarStyle"
- android:orientation="horizontal"
- android:measureWithLargestChild="true">
- <Space android:id="@+id/leftSpacer"
- android:layout_width="12dp"
- android:layout_height="match_parent"
- android:visibility="visible" />
- <!-- Negative Button -->
- <Button android:id="@+id/button2"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
- android:gravity="center"
- android:maxLines="2" />
- <Space android:id="@+id/middleSpacer"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:visibility="visible" />
- <!-- Positive Button -->
- <Button android:id="@+id/button1"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- style="@*android:style/Widget.DeviceDefault.Button.Colored"
- android:gravity="center"
- android:maxLines="2"
- android:text="@string/biometric_dialog_confirm"
- android:visibility="gone"/>
- <!-- Try Again Button -->
- <Button android:id="@+id/button_try_again"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- style="@*android:style/Widget.DeviceDefault.Button.Colored"
- android:gravity="center"
- android:maxLines="2"
- android:text="@string/biometric_dialog_try_again"
- android:visibility="gone"/>
- <Space android:id="@+id/rightSpacer"
- android:layout_width="12dip"
- android:layout_height="match_parent"
- android:visibility="visible" />
- </LinearLayout>
- </LinearLayout>
-
- <!-- This is not a Space since Spaces cannot be clicked. The width of this changes
- depending on horizontal/portrait orientation -->
- <View
- android:id="@+id/right_space"
- android:layout_weight="1"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:contentDescription="@string/biometric_dialog_empty_space_description"/>
-
- </LinearLayout>
-
- </ScrollView>
-
- </LinearLayout>
-
-</FrameLayout>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 38293bf..61210d3 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -480,4 +480,7 @@
<!-- Preferred refresh rate at keyguard, if supported by the display -->
<integer name="config_keyguardRefreshRate">-1</integer>
+ <!-- Whether or not to add a "people" notifications section -->
+ <bool name="config_usePeopleFiltering">false</bool>
+
</resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/PinnedStackListenerForwarder.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/PinnedStackListenerForwarder.java
index 2797042..3ae2df5b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/PinnedStackListenerForwarder.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/PinnedStackListenerForwarder.java
@@ -16,10 +16,9 @@
package com.android.systemui.shared.system;
-import android.content.ComponentName;
import android.content.pm.ParceledListSlice;
import android.graphics.Rect;
-import android.view.DisplayInfo;
+import android.os.RemoteException;
import android.view.IPinnedStackController;
import android.view.IPinnedStackListener;
@@ -33,132 +32,62 @@
* previously set listener.
*/
public class PinnedStackListenerForwarder extends IPinnedStackListener.Stub {
- private List<PinnedStackListener> mListeners = new ArrayList<>();
+ private List<IPinnedStackListener> mListeners = new ArrayList<>();
/** Adds a listener to receive updates from the WindowManagerService. */
- public void addListener(PinnedStackListener listener) {
+ public void addListener(IPinnedStackListener listener) {
mListeners.add(listener);
}
/** Removes a listener so it will no longer receive updates from the WindowManagerService. */
- public void removeListener(PinnedStackListener listener) {
+ public void removeListener(IPinnedStackListener listener) {
mListeners.remove(listener);
}
@Override
- public void onListenerRegistered(IPinnedStackController controller) {
- for (PinnedStackListener listener : mListeners) {
+ public void onListenerRegistered(IPinnedStackController controller) throws RemoteException {
+ for (IPinnedStackListener listener : mListeners) {
listener.onListenerRegistered(controller);
}
}
@Override
- public void onMovementBoundsChanged(Rect animatingBounds, boolean fromImeAdjustment,
- boolean fromShelfAdjustment) {
- for (PinnedStackListener listener : mListeners) {
- listener.onMovementBoundsChanged(animatingBounds, fromImeAdjustment,
- fromShelfAdjustment);
+ public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds, Rect animatingBounds,
+ boolean fromImeAdjustment, boolean fromShelfAdjustment, int displayRotation)
+ throws RemoteException {
+ for (IPinnedStackListener listener : mListeners) {
+ listener.onMovementBoundsChanged(
+ insetBounds, normalBounds, animatingBounds,
+ fromImeAdjustment, fromShelfAdjustment, displayRotation);
}
}
@Override
- public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
- for (PinnedStackListener listener : mListeners) {
+ public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) throws RemoteException {
+ for (IPinnedStackListener listener : mListeners) {
listener.onImeVisibilityChanged(imeVisible, imeHeight);
}
}
@Override
- public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {
- for (PinnedStackListener listener : mListeners) {
+ public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight)
+ throws RemoteException {
+ for (IPinnedStackListener listener : mListeners) {
listener.onShelfVisibilityChanged(shelfVisible, shelfHeight);
}
}
@Override
- public void onMinimizedStateChanged(boolean isMinimized) {
- for (PinnedStackListener listener : mListeners) {
+ public void onMinimizedStateChanged(boolean isMinimized) throws RemoteException {
+ for (IPinnedStackListener listener : mListeners) {
listener.onMinimizedStateChanged(isMinimized);
}
}
@Override
- public void onActionsChanged(ParceledListSlice actions) {
- for (PinnedStackListener listener : mListeners) {
+ public void onActionsChanged(ParceledListSlice actions) throws RemoteException {
+ for (IPinnedStackListener listener : mListeners) {
listener.onActionsChanged(actions);
}
}
-
- @Override
- public void onSaveReentrySnapFraction(ComponentName componentName, Rect bounds) {
- for (PinnedStackListener listener : mListeners) {
- listener.onSaveReentrySnapFraction(componentName, bounds);
- }
- }
-
- @Override
- public void onResetReentrySnapFraction(ComponentName componentName) {
- for (PinnedStackListener listener : mListeners) {
- listener.onResetReentrySnapFraction(componentName);
- }
- }
-
- @Override
- public void onDisplayInfoChanged(DisplayInfo displayInfo) {
- for (PinnedStackListener listener : mListeners) {
- listener.onDisplayInfoChanged(displayInfo);
- }
- }
-
- @Override
- public void onConfigurationChanged() {
- for (PinnedStackListener listener : mListeners) {
- listener.onConfigurationChanged();
- }
- }
-
- @Override
- public void onAspectRatioChanged(float aspectRatio) {
- for (PinnedStackListener listener : mListeners) {
- listener.onAspectRatioChanged(aspectRatio);
- }
- }
-
- @Override
- public void onPrepareAnimation(Rect sourceRectHint, float aspectRatio, Rect bounds) {
- for (PinnedStackListener listener : mListeners) {
- listener.onPrepareAnimation(sourceRectHint, aspectRatio, bounds);
- }
- }
-
- /**
- * A counterpart of {@link IPinnedStackListener} with empty implementations.
- * Subclasses can ignore those methods they do not intend to take action upon.
- */
- public static class PinnedStackListener {
- public void onListenerRegistered(IPinnedStackController controller) {}
-
- public void onMovementBoundsChanged(Rect animatingBounds, boolean fromImeAdjustment,
- boolean fromShelfAdjustment) {}
-
- public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {}
-
- public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {}
-
- public void onMinimizedStateChanged(boolean isMinimized) {}
-
- public void onActionsChanged(ParceledListSlice actions) {}
-
- public void onSaveReentrySnapFraction(ComponentName componentName, Rect bounds) {}
-
- public void onResetReentrySnapFraction(ComponentName componentName) {}
-
- public void onDisplayInfoChanged(DisplayInfo displayInfo) {}
-
- public void onConfigurationChanged() {}
-
- public void onAspectRatioChanged(float aspectRatio) {}
-
- public void onPrepareAnimation(Rect sourceRectHint, float aspectRatio, Rect bounds) {}
- }
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
index 9f1a1fa..794c30a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
@@ -27,12 +27,12 @@
import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
+import android.view.IPinnedStackListener;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
import com.android.systemui.shared.recents.view.RecentsTransition;
-import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener;
public class WindowManagerWrapper {
@@ -212,7 +212,7 @@
* Adds a pinned stack listener, which will receive updates from the window manager service
* along with any other pinned stack listeners that were added via this method.
*/
- public void addPinnedStackListener(PinnedStackListener listener) throws RemoteException {
+ public void addPinnedStackListener(IPinnedStackListener listener) throws RemoteException {
mPinnedStackListenerForwarder.addListener(listener);
WindowManagerGlobal.getWindowManagerService().registerPinnedStackListener(
DEFAULT_DISPLAY, mPinnedStackListenerForwarder);
@@ -221,7 +221,7 @@
/**
* Removes a pinned stack listener.
*/
- public void removePinnedStackListener(PinnedStackListener listener) {
+ public void removePinnedStackListener(IPinnedStackListener listener) {
mPinnedStackListenerForwarder.removeListener(listener);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index bd5b9c7..ca6803d 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -15,6 +15,7 @@
package com.android.systemui;
import android.annotation.Nullable;
+import android.app.AlarmManager;
import android.app.INotificationManager;
import android.content.res.Configuration;
import android.hardware.SensorPrivacyManager;
@@ -110,10 +111,10 @@
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.tuner.TunablePadding.TunablePaddingService;
import com.android.systemui.tuner.TunerService;
-import com.android.systemui.util.AsyncSensorManager;
import com.android.systemui.util.leak.GarbageMonitor;
import com.android.systemui.util.leak.LeakDetector;
import com.android.systemui.util.leak.LeakReporter;
+import com.android.systemui.util.sensors.AsyncSensorManager;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -314,6 +315,7 @@
@Inject Lazy<INotificationManager> mINotificationManager;
@Inject Lazy<FalsingManager> mFalsingManager;
@Inject Lazy<SysUiState> mSysUiStateFlagsContainer;
+ @Inject Lazy<AlarmManager> mAlarmManager;
@Inject
public Dependency() {
@@ -508,6 +510,7 @@
mProviders.put(INotificationManager.class, mINotificationManager::get);
mProviders.put(FalsingManager.class, mFalsingManager::get);
mProviders.put(SysUiState.class, mSysUiStateFlagsContainer::get);
+ mProviders.put(AlarmManager.class, mAlarmManager::get);
// TODO(b/118592525): to support multi-display , we start to add something which is
// per-display, while others may be global. I think it's time to add
diff --git a/packages/SystemUI/src/com/android/systemui/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/DependencyProvider.java
index d46a86c..239cbfe 100644
--- a/packages/SystemUI/src/com/android/systemui/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/DependencyProvider.java
@@ -23,6 +23,7 @@
import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
import android.annotation.Nullable;
+import android.app.AlarmManager;
import android.app.INotificationManager;
import android.content.Context;
import android.hardware.SensorPrivacyManager;
@@ -230,4 +231,11 @@
@Named(MAIN_HANDLER_NAME) Handler mainHandler) {
return new DeviceProvisionedControllerImpl(context, mainHandler);
}
+
+ /** */
+ @Singleton
+ @Provides
+ public AlarmManager provideAlarmManager(Context context) {
+ return context.getSystemService(AlarmManager.class);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/SystemUIModule.java
index ff4eb83..38f2f5e8 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIModule.java
@@ -24,7 +24,7 @@
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.KeyguardLiftController;
-import com.android.systemui.util.AsyncSensorManager;
+import com.android.systemui.util.sensors.AsyncSensorManager;
import javax.inject.Singleton;
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
index 1c5e800..8f1fcae 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
@@ -84,7 +84,7 @@
}
} else {
String svc = args[0].toLowerCase();
- if (Dependency.class.getName().endsWith(svc)) {
+ if (Dependency.class.getName().toLowerCase().endsWith(svc)) {
Dependency.staticDump(fd, pw, args);
}
for (SystemUI ui: services) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index e4d2005..73bbce9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -608,7 +608,10 @@
MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
}
- totalHeight += child.getMeasuredHeight();
+
+ if (child.getVisibility() != View.GONE) {
+ totalHeight += child.getMeasuredHeight();
+ }
}
// Use the new width so it's centered horizontally
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 43576a4..f86a6bd 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -48,6 +48,7 @@
import android.app.PendingIntent;
import android.content.Context;
import android.content.pm.ActivityInfo;
+import android.content.pm.ParceledListSlice;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.RemoteException;
@@ -60,6 +61,8 @@
import android.util.Pair;
import android.util.SparseSetArray;
import android.view.Display;
+import android.view.IPinnedStackController;
+import android.view.IPinnedStackListener;
import android.view.ViewGroup;
import android.widget.FrameLayout;
@@ -73,7 +76,6 @@
import com.android.systemui.R;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.PinnedStackListenerForwarder;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.WindowManagerWrapper;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -924,7 +926,9 @@
@Override
public void onSingleTaskDisplayDrawn(int displayId) {
- final Bubble expandedBubble = getExpandedBubble(mContext);
+ final Bubble expandedBubble = mStackView != null
+ ? mStackView.getExpandedBubble()
+ : null;
if (expandedBubble != null && expandedBubble.getDisplayId() == displayId) {
expandedBubble.setContentVisibility(true);
}
@@ -932,7 +936,9 @@
@Override
public void onSingleTaskDisplayEmpty(int displayId) {
- final Bubble expandedBubble = getExpandedBubble(mContext);
+ final Bubble expandedBubble = mStackView != null
+ ? mStackView.getExpandedBubble()
+ : null;
if (expandedBubble == null) {
return;
}
@@ -991,12 +997,32 @@
}
/** PinnedStackListener that dispatches IME visibility updates to the stack. */
- private class BubblesImeListener extends PinnedStackListenerForwarder.PinnedStackListener {
+ private class BubblesImeListener extends IPinnedStackListener.Stub {
+
+ @Override
+ public void onListenerRegistered(IPinnedStackController controller) throws RemoteException {
+ }
+
+ @Override
+ public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds,
+ Rect animatingBounds, boolean fromImeAdjustment, boolean fromShelfAdjustment,
+ int displayRotation) throws RemoteException {}
+
@Override
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
if (mStackView != null && mStackView.getBubbleCount() > 0) {
mStackView.post(() -> mStackView.onImeVisibilityChanged(imeVisible, imeHeight));
}
}
+
+ @Override
+ public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight)
+ throws RemoteException {}
+
+ @Override
+ public void onMinimizedStateChanged(boolean isMinimized) throws RemoteException {}
+
+ @Override
+ public void onActionsChanged(ParceledListSlice actions) throws RemoteException {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 81c8da8..dbc915a 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -184,6 +184,8 @@
Log.d(TAG, "notificationEntryUpdated: " + entry);
}
Bubble bubble = getBubbleWithKey(entry.key);
+ suppressFlyout = !entry.isVisuallyInterruptive || suppressFlyout;
+
if (bubble == null) {
// Create a new bubble
bubble = new Bubble(mContext, entry);
@@ -193,8 +195,10 @@
} else {
// Updates an existing bubble
bubble.updateEntry(entry);
+ bubble.setSuppressFlyout(suppressFlyout);
doUpdate(bubble);
}
+
if (bubble.shouldAutoExpand()) {
setSelectedBubbleInternal(bubble);
if (!mExpanded) {
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
index c2b0fe4..20742d6 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
@@ -43,7 +43,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.util.AsyncSensorManager;
+import com.android.systemui.util.sensors.AsyncSensorManager;
import java.io.PrintWriter;
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
index 85bc22b..914258f 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
@@ -20,6 +20,7 @@
import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
import android.content.Context;
+import android.hardware.SensorManager;
import android.net.Uri;
import android.os.Handler;
import android.provider.DeviceConfig;
@@ -35,7 +36,7 @@
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.util.DeviceConfigProxy;
-import com.android.systemui.util.ProximitySensor;
+import com.android.systemui.util.sensors.ProximitySensor;
import java.io.PrintWriter;
@@ -66,6 +67,7 @@
DeviceConfigProxy deviceConfig) {
mProximitySensor = proximitySensor;
mProximitySensor.setTag(PROXIMITY_SENSOR_TAG);
+ mProximitySensor.setSensorDelay(SensorManager.SENSOR_DELAY_GAME);
mDeviceConfig = deviceConfig;
mDeviceConfigListener =
properties -> onDeviceConfigPropertiesChanged(context, properties.getNamespace());
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
index 3f5cae6..0aa66af 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
@@ -30,7 +30,7 @@
import com.android.systemui.classifier.Classifier;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.util.DeviceConfigProxy;
-import com.android.systemui.util.ProximitySensor;
+import com.android.systemui.util.sensors.ProximitySensor;
import java.io.PrintWriter;
import java.util.ArrayList;
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java
index bf39751..53ca783 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java
@@ -19,7 +19,7 @@
import android.view.MotionEvent;
import com.android.systemui.classifier.Classifier;
-import com.android.systemui.util.ProximitySensor;
+import com.android.systemui.util.sensors.ProximitySensor;
import java.util.List;
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/ProximityClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/ProximityClassifier.java
index eeca409..f0feb4e 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/ProximityClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/ProximityClassifier.java
@@ -23,7 +23,7 @@
import android.view.MotionEvent;
import com.android.systemui.util.DeviceConfigProxy;
-import com.android.systemui.util.ProximitySensor;
+import com.android.systemui.util.sensors.ProximitySensor;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index c367286..bb8c7f1 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -33,7 +33,7 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.statusbar.phone.BiometricUnlockController;
import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.util.AsyncSensorManager;
+import com.android.systemui.util.sensors.AsyncSensorManager;
import com.android.systemui.util.wakelock.DelayedWakeLock;
import com.android.systemui.util.wakelock.WakeLock;
@@ -45,7 +45,7 @@
/** Creates a DozeMachine with its parts for {@code dozeService}. */
public DozeMachine assembleMachine(DozeService dozeService, FalsingManager falsingManager) {
Context context = dozeService;
- SensorManager sensorManager = Dependency.get(AsyncSensorManager.class);
+ AsyncSensorManager sensorManager = Dependency.get(AsyncSensorManager.class);
AlarmManager alarmManager = context.getSystemService(AlarmManager.class);
DockManager dockManager = Dependency.get(DockManager.class);
WakefulnessLifecycle wakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
@@ -91,7 +91,7 @@
params.getPolicy());
}
- private DozeTriggers createDozeTriggers(Context context, SensorManager sensorManager,
+ private DozeTriggers createDozeTriggers(Context context, AsyncSensorManager sensorManager,
DozeHost host, AlarmManager alarmManager, AmbientDisplayConfiguration config,
DozeParameters params, Handler handler, WakeLock wakeLock, DozeMachine machine,
DockManager dockManager) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 026a625..67eefc5 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -26,7 +26,6 @@
import android.content.Context;
import android.database.ContentObserver;
import android.hardware.Sensor;
-import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.TriggerEvent;
import android.hardware.TriggerEventListener;
@@ -43,11 +42,12 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
-import com.android.systemui.R;
+import com.android.systemui.Dependency;
import com.android.systemui.plugins.SensorManagerPlugin;
+import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.util.AlarmTimeout;
-import com.android.systemui.util.AsyncSensorManager;
+import com.android.systemui.util.sensors.AsyncSensorManager;
+import com.android.systemui.util.sensors.ProximitySensor;
import com.android.systemui.util.wakelock.WakeLock;
import java.io.PrintWriter;
@@ -62,7 +62,7 @@
private final Context mContext;
private final AlarmManager mAlarmManager;
- private final SensorManager mSensorManager;
+ private final AsyncSensorManager mSensorManager;
private final ContentResolver mResolver;
private final TriggerSensor mPickupSensor;
private final DozeParameters mDozeParameters;
@@ -74,13 +74,13 @@
protected TriggerSensor[] mSensors;
private final Handler mHandler = new Handler();
- private final ProxSensor mProxSensor;
+ private final ProximitySensor mProximitySensor;
private long mDebounceFrom;
private boolean mSettingRegistered;
private boolean mListening;
private boolean mPaused;
- public DozeSensors(Context context, AlarmManager alarmManager, SensorManager sensorManager,
+ public DozeSensors(Context context, AlarmManager alarmManager, AsyncSensorManager sensorManager,
DozeParameters dozeParameters, AmbientDisplayConfiguration config, WakeLock wakeLock,
Callback callback, Consumer<Boolean> proxCallback, AlwaysOnDisplayPolicy policy) {
mContext = context;
@@ -91,6 +91,7 @@
mWakeLock = wakeLock;
mProxCallback = proxCallback;
mResolver = mContext.getContentResolver();
+ mCallback = callback;
boolean alwaysOn = mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT);
mSensors = new TriggerSensor[] {
@@ -146,8 +147,11 @@
false /* touchscreen */, mConfig.getWakeLockScreenDebounce()),
};
- mProxSensor = new ProxSensor(policy);
- mCallback = callback;
+ mProximitySensor = new ProximitySensor(
+ context, sensorManager, Dependency.get(PluginManager.class));
+
+ mProximitySensor.register(
+ proximityEvent -> mProxCallback.accept(!proximityEvent.getNear()));
}
/**
@@ -236,7 +240,15 @@
}
public void setProxListening(boolean listen) {
- mProxSensor.setRequested(listen);
+ if (mProximitySensor.isRegistered() && listen) {
+ mProximitySensor.alertListeners();
+ } else {
+ if (listen) {
+ mProximitySensor.resume();
+ } else {
+ mProximitySensor.pause();
+ }
+ }
}
private final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
@@ -267,115 +279,16 @@
/** Dump current state */
public void dump(PrintWriter pw) {
for (TriggerSensor s : mSensors) {
- pw.print(" Sensor: "); pw.println(s.toString());
+ pw.println(" Sensor: " + s.toString());
}
- pw.print(" ProxSensor: "); pw.println(mProxSensor.toString());
+ pw.println(" ProxSensor: " + mProximitySensor.toString());
}
/**
- * @return true if prox is currently far, false if near or null if unknown.
+ * @return true if prox is currently near, false if far or null if unknown.
*/
- public Boolean isProximityCurrentlyFar() {
- return mProxSensor.mCurrentlyFar;
- }
-
- private class ProxSensor implements SensorEventListener {
-
- boolean mRequested;
- boolean mRegistered;
- Boolean mCurrentlyFar;
- long mLastNear;
- final AlarmTimeout mCooldownTimer;
- final AlwaysOnDisplayPolicy mPolicy;
- final Sensor mSensor;
- final boolean mUsingBrightnessSensor;
-
- public ProxSensor(AlwaysOnDisplayPolicy policy) {
- mPolicy = policy;
- mCooldownTimer = new AlarmTimeout(mAlarmManager, this::updateRegistered,
- "prox_cooldown", mHandler);
-
- // The default prox sensor can be noisy, so let's use a prox gated brightness sensor
- // if available.
- Sensor sensor = DozeSensors.findSensorWithType(mSensorManager,
- mContext.getString(R.string.doze_brightness_sensor_type));
- mUsingBrightnessSensor = sensor != null;
- if (sensor == null) {
- sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
- }
- mSensor = sensor;
- }
-
- void setRequested(boolean requested) {
- if (mRequested == requested) {
- // Send an update even if we don't re-register.
- mHandler.post(() -> {
- if (mCurrentlyFar != null) {
- mProxCallback.accept(mCurrentlyFar);
- }
- });
- return;
- }
- mRequested = requested;
- updateRegistered();
- }
-
- private void updateRegistered() {
- setRegistered(mRequested && !mCooldownTimer.isScheduled());
- }
-
- private void setRegistered(boolean register) {
- if (mRegistered == register) {
- return;
- }
- if (register) {
- mRegistered = mSensorManager.registerListener(this, mSensor,
- SensorManager.SENSOR_DELAY_NORMAL, mHandler);
- } else {
- mSensorManager.unregisterListener(this);
- mRegistered = false;
- mCurrentlyFar = null;
- }
- }
-
- @Override
- public void onSensorChanged(android.hardware.SensorEvent event) {
- if (DEBUG) Log.d(TAG, "onSensorChanged " + event);
-
- if (mUsingBrightnessSensor) {
- // The custom brightness sensor is gated by the proximity sensor and will return 0
- // whenever prox is covered.
- mCurrentlyFar = event.values[0] > 0;
- } else {
- mCurrentlyFar = event.values[0] >= event.sensor.getMaximumRange();
- }
- mProxCallback.accept(mCurrentlyFar);
-
- long now = SystemClock.elapsedRealtime();
- if (mCurrentlyFar == null) {
- // Sensor has been unregistered by the proxCallback. Do nothing.
- } else if (!mCurrentlyFar) {
- mLastNear = now;
- } else if (mCurrentlyFar && now - mLastNear < mPolicy.proxCooldownTriggerMs) {
- // If the last near was very recent, we might be using more power for prox
- // wakeups than we're saving from turning of the screen. Instead, turn it off
- // for a while.
- mCooldownTimer.schedule(mPolicy.proxCooldownPeriodMs,
- AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
- updateRegistered();
- }
- }
-
- @Override
- public void onAccuracyChanged(Sensor sensor, int accuracy) {
- }
-
- @Override
- public String toString() {
- return String.format("{registered=%s, requested=%s, coolingDown=%s, currentlyFar=%s,"
- + " sensor=%s}", mRegistered, mRequested, mCooldownTimer.isScheduled(),
- mCurrentlyFar, mSensor);
- }
+ public Boolean isProximityCurrentlyNear() {
+ return mProximitySensor.isNear();
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index bab64db..80d4b63 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -24,10 +24,6 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
import android.hardware.display.AmbientDisplayConfiguration;
import android.metrics.LogMaker;
import android.os.Handler;
@@ -39,16 +35,16 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.util.Preconditions;
import com.android.systemui.Dependency;
-import com.android.systemui.R;
import com.android.systemui.dock.DockManager;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.util.Assert;
+import com.android.systemui.util.sensors.AsyncSensorManager;
+import com.android.systemui.util.sensors.ProximitySensor;
import com.android.systemui.util.wakelock.WakeLock;
import java.io.PrintWriter;
-import java.util.function.IntConsumer;
+import java.util.function.Consumer;
/**
* Handles triggers for ambient state changes.
@@ -67,13 +63,15 @@
*/
private static boolean sWakeDisplaySensorState = true;
+ private static final int PROXIMITY_TIMEOUT_DELAY_MS = 500;
+
private final Context mContext;
private final DozeMachine mMachine;
private final DozeSensors mDozeSensors;
private final DozeHost mDozeHost;
private final AmbientDisplayConfiguration mConfig;
private final DozeParameters mDozeParameters;
- private final SensorManager mSensorManager;
+ private final AsyncSensorManager mSensorManager;
private final Handler mHandler;
private final WakeLock mWakeLock;
private final boolean mAllowPulseTriggers;
@@ -81,6 +79,7 @@
private final TriggerReceiver mBroadcastReceiver = new TriggerReceiver();
private final DockEventListener mDockEventListener = new DockEventListener();
private final DockManager mDockManager;
+ private final ProximitySensor.ProximityCheck mProxCheck;
private long mNotificationPulseTime;
private boolean mPulsePending;
@@ -89,7 +88,7 @@
public DozeTriggers(Context context, DozeMachine machine, DozeHost dozeHost,
AlarmManager alarmManager, AmbientDisplayConfiguration config,
- DozeParameters dozeParameters, SensorManager sensorManager, Handler handler,
+ DozeParameters dozeParameters, AsyncSensorManager sensorManager, Handler handler,
WakeLock wakeLock, boolean allowPulseTriggers, DockManager dockManager) {
mContext = context;
mMachine = machine;
@@ -105,6 +104,9 @@
dozeParameters.getPolicy());
mUiModeManager = mContext.getSystemService(UiModeManager.class);
mDockManager = dockManager;
+ mProxCheck = new ProximitySensor.ProximityCheck(
+ new ProximitySensor(mContext, mSensorManager, null),
+ mHandler);
}
private void onNotification(Runnable onPulseSuppressedListener) {
@@ -134,25 +136,27 @@
}
}
- private void proximityCheckThenCall(IntConsumer callback,
+ private void proximityCheckThenCall(Consumer<Boolean> callback,
boolean alreadyPerformedProxCheck,
int reason) {
- Boolean cachedProxFar = mDozeSensors.isProximityCurrentlyFar();
+ Boolean cachedProxNear = mDozeSensors.isProximityCurrentlyNear();
if (alreadyPerformedProxCheck) {
- callback.accept(ProximityCheck.RESULT_NOT_CHECKED);
- } else if (cachedProxFar != null) {
- callback.accept(cachedProxFar ? ProximityCheck.RESULT_FAR : ProximityCheck.RESULT_NEAR);
+ callback.accept(null);
+ } else if (cachedProxNear != null) {
+ callback.accept(cachedProxNear);
} else {
final long start = SystemClock.uptimeMillis();
- new ProximityCheck() {
- @Override
- public void onProximityResult(int result) {
- final long end = SystemClock.uptimeMillis();
- DozeLog.traceProximityResult(mContext, result == RESULT_NEAR,
- end - start, reason);
- callback.accept(result);
- }
- }.check();
+ mProxCheck.check(PROXIMITY_TIMEOUT_DELAY_MS, near -> {
+ final long end = SystemClock.uptimeMillis();
+ DozeLog.traceProximityResult(
+ mContext,
+ near == null ? false : near,
+ end - start,
+ reason);
+ callback.accept(near);
+ mWakeLock.release(TAG);
+ });
+ mWakeLock.acquire(TAG);
}
}
@@ -178,7 +182,7 @@
}
} else {
proximityCheckThenCall((result) -> {
- if (result == ProximityCheck.RESULT_NEAR) {
+ if (result) {
// In pocket, drop event.
return;
}
@@ -267,7 +271,7 @@
if (wake) {
proximityCheckThenCall((result) -> {
- if (result == ProximityCheck.RESULT_NEAR) {
+ if (result) {
// In pocket, drop event.
return;
}
@@ -376,7 +380,7 @@
mPulsePending = true;
proximityCheckThenCall((result) -> {
- if (result == ProximityCheck.RESULT_NEAR) {
+ if (result) {
// in pocket, abort pulse
DozeLog.tracePulseDropped(mContext, "inPocket");
mPulsePending = false;
@@ -412,104 +416,11 @@
pw.print(" notificationPulseTime=");
pw.println(Formatter.formatShortElapsedTime(mContext, mNotificationPulseTime));
- pw.print(" pulsePending="); pw.println(mPulsePending);
+ pw.println(" pulsePending=" + mPulsePending);
pw.println("DozeSensors:");
mDozeSensors.dump(pw);
}
- /**
- * @see DozeSensors.ProxSensor
- */
- private abstract class ProximityCheck implements SensorEventListener, Runnable {
- private static final int TIMEOUT_DELAY_MS = 500;
-
- protected static final int RESULT_UNKNOWN = 0;
- protected static final int RESULT_NEAR = 1;
- protected static final int RESULT_FAR = 2;
- protected static final int RESULT_NOT_CHECKED = 3;
-
- private boolean mRegistered;
- private boolean mFinished;
- private float mMaxRange;
- private boolean mUsingBrightnessSensor;
-
- protected abstract void onProximityResult(int result);
-
- public void check() {
- Preconditions.checkState(!mFinished && !mRegistered);
- Sensor sensor = DozeSensors.findSensorWithType(mSensorManager,
- mContext.getString(R.string.doze_brightness_sensor_type));
- mUsingBrightnessSensor = sensor != null;
- if (sensor == null) {
- sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
- }
- if (sensor == null) {
- if (DozeMachine.DEBUG) Log.d(TAG, "ProxCheck: No sensor found");
- finishWithResult(RESULT_UNKNOWN);
- return;
- }
- mDozeSensors.setDisableSensorsInterferingWithProximity(true);
-
- mMaxRange = sensor.getMaximumRange();
- mSensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL, 0,
- mHandler);
- mHandler.postDelayed(this, TIMEOUT_DELAY_MS);
- mWakeLock.acquire(TAG);
- mRegistered = true;
- }
-
- /**
- * @see DozeSensors.ProxSensor#onSensorChanged(SensorEvent)
- */
- @Override
- public void onSensorChanged(SensorEvent event) {
- if (event.values.length == 0) {
- if (DozeMachine.DEBUG) Log.d(TAG, "ProxCheck: Event has no values!");
- finishWithResult(RESULT_UNKNOWN);
- } else {
- if (DozeMachine.DEBUG) {
- Log.d(TAG, "ProxCheck: Event: value=" + event.values[0] + " max=" + mMaxRange);
- }
- final boolean isNear;
- if (mUsingBrightnessSensor) {
- // The custom brightness sensor is gated by the proximity sensor and will
- // return 0 whenever prox is covered.
- isNear = event.values[0] == 0;
- } else {
- isNear = event.values[0] < mMaxRange;
- }
- finishWithResult(isNear ? RESULT_NEAR : RESULT_FAR);
- }
- }
-
- @Override
- public void run() {
- if (DozeMachine.DEBUG) Log.d(TAG, "ProxCheck: No event received before timeout");
- finishWithResult(RESULT_UNKNOWN);
- }
-
- private void finishWithResult(int result) {
- if (mFinished) return;
- boolean wasRegistered = mRegistered;
- if (mRegistered) {
- mHandler.removeCallbacks(this);
- mSensorManager.unregisterListener(this);
- mDozeSensors.setDisableSensorsInterferingWithProximity(false);
- mRegistered = false;
- }
- onProximityResult(result);
- if (wasRegistered) {
- mWakeLock.release(TAG);
- }
- mFinished = true;
- }
-
- @Override
- public void onAccuracyChanged(Sensor sensor, int accuracy) {
- // noop
- }
- }
-
private class TriggerReceiver extends BroadcastReceiver {
private boolean mRegistered;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
index 6795bff..30be775 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
@@ -18,7 +18,6 @@
import static android.util.TypedValue.COMPLEX_UNIT_DIP;
-import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
@@ -47,6 +46,9 @@
private static final String TAG = PipBoundsHandler.class.getSimpleName();
private static final float INVALID_SNAP_FRACTION = -1f;
+ // System.identityHashCode guarantees zero for null object.
+ private static final int INVALID_SYSTEM_IDENTITY_TOKEN = 0;
+
private final Context mContext;
private final IWindowManager mWindowManager;
private final PipSnapAlgorithm mSnapAlgorithm;
@@ -56,7 +58,7 @@
private final Point mTmpDisplaySize = new Point();
private IPinnedStackController mPinnedStackController;
- private ComponentName mLastPipComponentName;
+ private int mLastPipToken;
private float mReentrySnapFraction = INVALID_SNAP_FRACTION;
private float mDefaultAspectRatio;
@@ -78,11 +80,8 @@
mContext = context;
mSnapAlgorithm = new PipSnapAlgorithm(context);
mWindowManager = WindowManagerGlobal.getWindowManagerService();
- reloadResources();
- // Initialize the aspect ratio to the default aspect ratio. Don't do this in reload
- // resources as it would clobber mAspectRatio when entering PiP from fullscreen which
- // triggers a configuration change and the resources to be reloaded.
mAspectRatio = mDefaultAspectRatio;
+ reloadResources();
}
/**
@@ -162,27 +161,27 @@
}
/**
- * Responds to IPinnedStackListener on saving reentry snap fraction
- * for a given {@link ComponentName}.
+ * Responds to IPinnedStackListener on saving reentry snap fraction for a given token.
+ * Token should be generated via {@link System#identityHashCode(Object)}
*/
- public void onSaveReentrySnapFraction(ComponentName componentName, Rect bounds) {
- mReentrySnapFraction = getSnapFraction(bounds);
- mLastPipComponentName = componentName;
+ public void onSaveReentrySnapFraction(int token, Rect stackBounds) {
+ mReentrySnapFraction = getSnapFraction(stackBounds);
+ mLastPipToken = token;
}
/**
- * Responds to IPinnedStackListener on resetting reentry snap fraction
- * for a given {@link ComponentName}.
+ * Responds to IPinnedStackListener on resetting reentry snap fraction for a given token.
+ * Token should be generated via {@link System#identityHashCode(Object)}
*/
- public void onResetReentrySnapFraction(ComponentName componentName) {
- if (componentName.equals(mLastPipComponentName)) {
+ public void onResetReentrySnapFraction(int token) {
+ if (mLastPipToken == token) {
onResetReentrySnapFractionUnchecked();
}
}
private void onResetReentrySnapFractionUnchecked() {
mReentrySnapFraction = INVALID_SNAP_FRACTION;
- mLastPipComponentName = null;
+ mLastPipToken = INVALID_SYSTEM_IDENTITY_TOKEN;
}
/**
@@ -213,28 +212,24 @@
/**
* Responds to IPinnedStackListener on preparing the pinned stack animation.
*/
- public void onPrepareAnimation(Rect sourceRectHint, float aspectRatio, Rect bounds) {
- final Rect destinationBounds;
- if (bounds == null) {
- destinationBounds = getDefaultBounds(mReentrySnapFraction);
+ public void onPrepareAnimation(Rect sourceRectHint, float aspectRatio, Rect stackBounds) {
+ final Rect targetStackBounds;
+ if (stackBounds == null) {
+ targetStackBounds = getDefaultBounds(mReentrySnapFraction);
} else {
- destinationBounds = new Rect(bounds);
+ targetStackBounds = new Rect();
+ targetStackBounds.set(stackBounds);
}
if (isValidPictureInPictureAspectRatio(aspectRatio)) {
- transformBoundsToAspectRatio(destinationBounds, aspectRatio,
- false /* useCurrentMinEdgeSize */);
+ transformBoundsToAspectRatio(targetStackBounds, aspectRatio,
+ true /* useCurrentMinEdgeSize */);
}
- if (destinationBounds.equals(bounds)) {
+ if (targetStackBounds.equals(stackBounds)) {
return;
}
mAspectRatio = aspectRatio;
onResetReentrySnapFractionUnchecked();
- try {
- mPinnedStackController.startAnimation(destinationBounds, sourceRectHint,
- -1 /* animationDuration */);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to start PiP animation from SysUI", e);
- }
+ // TODO: callback Window Manager on starting animation with calculated bounds
}
/**
@@ -363,7 +358,6 @@
public void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
- pw.println(innerPrefix + "mLastPipComponentName=" + mLastPipComponentName);
pw.println(innerPrefix + "mReentrySnapFraction=" + mReentrySnapFraction);
pw.println(innerPrefix + "mDisplayInfo=" + mDisplayInfo);
pw.println(innerPrefix + "mDefaultAspectRatio=" + mDefaultAspectRatio);
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 8dfae32..3be3422 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -32,16 +32,14 @@
import android.os.RemoteException;
import android.util.Log;
import android.util.Pair;
-import android.view.DisplayInfo;
import android.view.IPinnedStackController;
+import android.view.IPinnedStackListener;
import com.android.systemui.Dependency;
import com.android.systemui.UiOffloadThread;
import com.android.systemui.pip.BasePipManager;
-import com.android.systemui.pip.PipBoundsHandler;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.InputConsumerController;
-import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.WindowManagerWrapper;
@@ -60,12 +58,8 @@
private IActivityTaskManager mActivityTaskManager;
private Handler mHandler = new Handler();
- private final PinnedStackListener mPinnedStackListener = new PipManagerPinnedStackListener();
- private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
- private final Rect mTmpInsetBounds = new Rect();
- private final Rect mTmpNormalBounds = new Rect();
+ private final PinnedStackListener mPinnedStackListener = new PinnedStackListener();
- private PipBoundsHandler mPipBoundsHandler;
private InputConsumerController mInputConsumerController;
private PipMenuActivityController mMenuController;
private PipMediaController mMediaController;
@@ -125,11 +119,11 @@
/**
* Handler for messages from the PIP controller.
*/
- private class PipManagerPinnedStackListener extends PinnedStackListener {
+ private class PinnedStackListener extends IPinnedStackListener.Stub {
+
@Override
public void onListenerRegistered(IPinnedStackController controller) {
mHandler.post(() -> {
- mPipBoundsHandler.setPinnedStackController(controller);
mTouchHandler.setPinnedStackController(controller);
});
}
@@ -137,7 +131,6 @@
@Override
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
mHandler.post(() -> {
- mPipBoundsHandler.onImeVisibilityChanged(imeVisible, imeHeight);
mTouchHandler.onImeVisibilityChanged(imeVisible, imeHeight);
});
}
@@ -145,66 +138,31 @@
@Override
public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {
mHandler.post(() -> {
- mPipBoundsHandler.onShelfVisibilityChanged(shelfVisible, shelfHeight);
- mTouchHandler.onShelfVisibilityChanged(shelfVisible, shelfHeight);
+ mTouchHandler.onShelfVisibilityChanged(shelfVisible, shelfHeight);
});
}
@Override
public void onMinimizedStateChanged(boolean isMinimized) {
mHandler.post(() -> {
- mPipBoundsHandler.onMinimizedStateChanged(isMinimized);
mTouchHandler.setMinimizedState(isMinimized, true /* fromController */);
});
}
@Override
- public void onMovementBoundsChanged(Rect animatingBounds, boolean fromImeAdjustment,
- boolean fromShelfAdjustment) {
+ public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds,
+ Rect animatingBounds, boolean fromImeAdjustment, boolean fromShelfAdjustment,
+ int displayRotation) {
mHandler.post(() -> {
- // Populate the inset / normal bounds and DisplayInfo from mPipBoundsHandler first.
- mPipBoundsHandler.onMovementBoundsChanged(mTmpInsetBounds, mTmpNormalBounds,
- animatingBounds, mTmpDisplayInfo);
- mTouchHandler.onMovementBoundsChanged(mTmpInsetBounds, mTmpNormalBounds,
- animatingBounds, fromImeAdjustment, fromShelfAdjustment,
- mTmpDisplayInfo.rotation);
+ mTouchHandler.onMovementBoundsChanged(insetBounds, normalBounds, animatingBounds,
+ fromImeAdjustment, fromShelfAdjustment, displayRotation);
});
}
@Override
public void onActionsChanged(ParceledListSlice actions) {
- mHandler.post(() -> mMenuController.setAppActions(actions));
- }
-
- @Override
- public void onSaveReentrySnapFraction(ComponentName componentName, Rect bounds) {
- mHandler.post(() -> mPipBoundsHandler.onSaveReentrySnapFraction(componentName, bounds));
- }
-
- @Override
- public void onResetReentrySnapFraction(ComponentName componentName) {
- mHandler.post(() -> mPipBoundsHandler.onResetReentrySnapFraction(componentName));
- }
-
- @Override
- public void onDisplayInfoChanged(DisplayInfo displayInfo) {
- mHandler.post(() -> mPipBoundsHandler.onDisplayInfoChanged(displayInfo));
- }
-
- @Override
- public void onConfigurationChanged() {
- mHandler.post(() -> mPipBoundsHandler.onConfigurationChanged());
- }
-
- @Override
- public void onAspectRatioChanged(float aspectRatio) {
- mHandler.post(() -> mPipBoundsHandler.onAspectRatioChanged(aspectRatio));
- }
-
- @Override
- public void onPrepareAnimation(Rect sourceRectHint, float aspectRatio, Rect bounds) {
mHandler.post(() -> {
- mPipBoundsHandler.onPrepareAnimation(sourceRectHint, aspectRatio, bounds);
+ mMenuController.setAppActions(actions);
});
}
}
@@ -226,13 +184,12 @@
}
ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
- mPipBoundsHandler = new PipBoundsHandler(context);
mInputConsumerController = InputConsumerController.getPipInputConsumer();
mMediaController = new PipMediaController(context, mActivityManager);
mMenuController = new PipMenuActivityController(context, mActivityManager, mMediaController,
mInputConsumerController);
mTouchHandler = new PipTouchHandler(context, mActivityManager, mActivityTaskManager,
- mMenuController, mInputConsumerController, mPipBoundsHandler);
+ mMenuController, mInputConsumerController);
mAppOpsListener = new PipAppOpsListener(context, mActivityManager,
mTouchHandler.getMotionHelper());
@@ -295,6 +252,5 @@
mInputConsumerController.dump(pw, innerPrefix);
mMenuController.dump(pw, innerPrefix);
mTouchHandler.dump(pw, innerPrefix);
- mPipBoundsHandler.dump(pw, innerPrefix);
}
}
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 1f36d97..30cf412 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -48,7 +48,6 @@
import com.android.internal.os.logging.MetricsLoggerWrapper;
import com.android.internal.policy.PipSnapAlgorithm;
import com.android.systemui.R;
-import com.android.systemui.pip.PipBoundsHandler;
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.statusbar.FlingAnimationUtils;
@@ -76,7 +75,6 @@
private final IActivityTaskManager mActivityTaskManager;
private final ViewConfiguration mViewConfig;
private final PipMenuListener mMenuListener = new PipMenuListener();
- private final PipBoundsHandler mPipBoundsHandler;
private IPinnedStackController mPinnedStackController;
private final PipMenuActivityController mMenuController;
@@ -180,8 +178,7 @@
public PipTouchHandler(Context context, IActivityManager activityManager,
IActivityTaskManager activityTaskManager, PipMenuActivityController menuController,
- InputConsumerController inputConsumerController,
- PipBoundsHandler pipBoundsHandler) {
+ InputConsumerController inputConsumerController) {
// Initialize the Pip input consumer
mContext = context;
@@ -214,8 +211,6 @@
inputConsumerController.setInputListener(this::handleTouchEvent);
inputConsumerController.setRegistrationListener(this::onRegistrationChanged);
onRegistrationChanged(inputConsumerController.isRegistered());
-
- mPipBoundsHandler = pipBoundsHandler;
}
public void setTouchEnabled(boolean enabled) {
@@ -792,8 +787,14 @@
mMovementBounds = isMenuExpanded
? mExpandedMovementBounds
: mNormalMovementBounds;
- mPipBoundsHandler.setMinEdgeSize(
- isMenuExpanded ? mExpandedShortestEdgeSize : 0);
+ try {
+ if (mPinnedStackController != null) {
+ mPinnedStackController.setMinEdgeSize(
+ isMenuExpanded ? mExpandedShortestEdgeSize : 0);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not set minimized state", e);
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index 81d6973..918af4f 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -19,10 +19,13 @@
import static android.app.ActivityTaskManager.INVALID_STACK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.view.Display.DEFAULT_DISPLAY;
+import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityManager.StackInfo;
import android.app.ActivityTaskManager;
+import android.app.IActivityManager;
import android.app.IActivityTaskManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -43,15 +46,16 @@
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
-import android.view.DisplayInfo;
+import android.view.IPinnedStackController;
+import android.view.IPinnedStackListener;
+import android.view.IWindowManager;
+import android.view.WindowManagerGlobal;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.UiOffloadThread;
import com.android.systemui.pip.BasePipManager;
-import com.android.systemui.pip.PipBoundsHandler;
import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.WindowManagerWrapper;
@@ -107,8 +111,9 @@
private int mSuspendPipResizingReason;
private Context mContext;
- private PipBoundsHandler mPipBoundsHandler;
+ private IActivityManager mActivityManager;
private IActivityTaskManager mActivityTaskManager;
+ private IWindowManager mWindowManager;
private MediaSessionManager mMediaSessionManager;
private int mState = STATE_NO_PIP;
private int mResumeResizePinnedStackRunnableState = STATE_NO_PIP;
@@ -130,16 +135,11 @@
private PipNotification mPipNotification;
private ParceledListSlice mCustomActions;
- // Used to calculate the movement bounds
- private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
- private final Rect mTmpInsetBounds = new Rect();
- private final Rect mTmpNormalBounds = new Rect();
-
// Keeps track of the IME visibility to adjust the PiP when the IME is visible
private boolean mImeVisible;
private int mImeHeightAdjustment;
- private final PinnedStackListener mPinnedStackListener = new PipManagerPinnedStackListener();
+ private final PinnedStackListener mPinnedStackListener = new PinnedStackListener();
private final Runnable mResizePinnedStackRunnable = new Runnable() {
@Override
@@ -181,7 +181,11 @@
/**
* Handler for messages from the PIP controller.
*/
- private class PipManagerPinnedStackListener extends PinnedStackListener {
+ private class PinnedStackListener extends IPinnedStackListener.Stub {
+
+ @Override
+ public void onListenerRegistered(IPinnedStackController controller) {}
+
@Override
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
if (mState == STATE_PIP) {
@@ -201,13 +205,17 @@
}
@Override
- public void onMovementBoundsChanged(Rect animatingBounds, boolean fromImeAdjustment,
- boolean fromShelfAdjustment) {
+ public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {}
+
+ @Override
+ public void onMinimizedStateChanged(boolean isMinimized) {}
+
+ @Override
+ public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds,
+ Rect animatingBounds, boolean fromImeAdjustment, boolean fromShelfAdjustment,
+ int displayRotation) {
mHandler.post(() -> {
- // Populate the inset / normal bounds and DisplayInfo from mPipBoundsHandler first.
- mPipBoundsHandler.onMovementBoundsChanged(mTmpInsetBounds, mTmpNormalBounds,
- animatingBounds, mTmpDisplayInfo);
- mDefaultPipBounds.set(animatingBounds);
+ mDefaultPipBounds.set(normalBounds);
});
}
@@ -233,8 +241,10 @@
}
mInitialized = true;
mContext = context;
- mPipBoundsHandler = new PipBoundsHandler(context);
+
+ mActivityManager = ActivityManager.getService();
mActivityTaskManager = ActivityTaskManager.getService();
+ mWindowManager = WindowManagerGlobal.getWindowManagerService();
ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_MEDIA_RESOURCE_GRANTED);
@@ -281,7 +291,7 @@
(MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
try {
- WindowManagerWrapper.getInstance().addPinnedStackListener(mPinnedStackListener);
+ mWindowManager.registerPinnedStackListener(DEFAULT_DISPLAY, mPinnedStackListener);
} catch (RemoteException e) {
Log.e(TAG, "Failed to register pinned stack listener", e);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 61d7498..1e763cf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -222,7 +222,7 @@
if (!TILES_SETTING.equals(key)) {
return;
}
- if (DEBUG) Log.d(TAG, "Recreating tiles");
+ Log.d(TAG, "Recreating tiles");
if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) {
newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode);
}
@@ -231,7 +231,7 @@
if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return;
mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach(
tile -> {
- if (DEBUG) Log.d(TAG, "Destroying tile: " + tile.getKey());
+ Log.d(TAG, "Destroying tile: " + tile.getKey());
tile.getValue().destroy();
});
final LinkedHashMap<String, QSTile> newTiles = new LinkedHashMap<>();
@@ -248,9 +248,10 @@
newTiles.put(tileSpec, tile);
} else {
tile.destroy();
+ Log.d(TAG, "Destroying not available tile: " + tileSpec);
}
} else {
- if (DEBUG) Log.d(TAG, "Creating tile: " + tileSpec);
+ Log.d(TAG, "Creating tile: " + tileSpec);
try {
tile = createTile(tileSpec);
if (tile != null) {
@@ -259,6 +260,7 @@
newTiles.put(tileSpec, tile);
} else {
tile.destroy();
+ Log.d(TAG, "Destroying not available tile: " + tileSpec);
}
}
} catch (Throwable t) {
@@ -274,7 +276,7 @@
mTiles.putAll(newTiles);
if (newTiles.isEmpty() && !tileSpecs.isEmpty()) {
// If we didn't manage to create any tiles, set it to empty (default)
- if (DEBUG) Log.d(TAG, "No valid tiles on tuning changed. Setting to default.");
+ Log.d(TAG, "No valid tiles on tuning changed. Setting to default.");
changeTiles(currentSpecs, loadTileSpecs(mContext, ""));
} else {
for (int i = 0; i < mCallbacks.size(); i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 289277e..f782fab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -18,6 +18,7 @@
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
import static com.android.systemui.DejankUtils.whitelistIpcs;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
import android.app.ActivityManager;
import android.app.KeyguardManager;
@@ -316,7 +317,7 @@
boolean exceedsPriorityThreshold;
if (NotificationUtils.useNewInterruptionModel(mContext)
&& hideSilentNotificationsOnLockscreen()) {
- exceedsPriorityThreshold = entry.isTopBucket();
+ exceedsPriorityThreshold = entry.getBucket() != BUCKET_SILENT;
} else {
exceedsPriorityThreshold =
!getEntryManager().getNotificationData().isAmbient(entry.key);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index 276afa7..a70dc7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -33,6 +33,7 @@
import com.android.systemui.Interpolators
import com.android.systemui.R
import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
@@ -56,7 +57,8 @@
private val wakeUpCoordinator: NotificationWakeUpCoordinator,
private val bypassController: KeyguardBypassController,
private val headsUpManager: HeadsUpManagerPhone,
- private val roundnessManager: NotificationRoundnessManager
+ private val roundnessManager: NotificationRoundnessManager,
+ private val statusBarStateController: StatusBarStateController
) : Gefingerpoken {
companion object {
private val RUBBERBAND_FACTOR_STATIC = 0.25f
@@ -188,7 +190,8 @@
MotionEvent.ACTION_MOVE -> updateExpansionHeight(moveDistance)
MotionEvent.ACTION_UP -> {
velocityTracker!!.computeCurrentVelocity(1000 /* units */)
- val canExpand = moveDistance > 0 && velocityTracker!!.getYVelocity() > -1000
+ val canExpand = moveDistance > 0 && velocityTracker!!.getYVelocity() > -1000 &&
+ statusBarStateController.state != StatusBarState.SHADE
if (!mFalsingManager.isUnlockingDisabled && !isFalseTouch && canExpand) {
finishExpansion()
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 4422a81..8b9268e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -20,6 +20,7 @@
import android.animation.ValueAnimator;
import android.text.format.DateFormat;
import android.util.FloatProperty;
+import android.util.Log;
import android.view.View;
import android.view.animation.Interpolator;
@@ -137,6 +138,11 @@
// Record the to-be mState and mLastState
recordHistoricalState(state, mState);
+ // b/139259891
+ if (mState == StatusBarState.SHADE && state == StatusBarState.SHADE_LOCKED) {
+ Log.e(TAG, "Invalid state transition: SHADE -> SHADE_LOCKED", new Throwable());
+ }
+
synchronized (mListeners) {
String tag = getClass().getSimpleName() + "#setState(" + state + ")";
DejankUtils.startDetectingBlockingIpcs(tag);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 4a27a4e..b6b149d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -124,7 +124,7 @@
@Inject
public NotificationEntryManager(Context context) {
- mNotificationData = new NotificationData();
+ mNotificationData = new NotificationData(context);
}
/** Adds a {@link NotificationEntryListener}. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
index 1af47dd..dfbbf98 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
@@ -36,6 +36,7 @@
private static final int[] sLocationOffset = new int[2];
@Nullable private static Boolean sUseNewInterruptionModel = null;
+ @Nullable private static Boolean sUsePeopleFiltering = null;
public static boolean isGrayscale(ImageView v, ContrastColorUtil colorUtil) {
Object isGrayscale = v.getTag(R.id.icon_is_grayscale);
@@ -87,4 +88,17 @@
}
return sUseNewInterruptionModel;
}
+
+ /**
+ * Caches and returns the value of the people filtering setting. Cannot change except through
+ * process restarts.
+ */
+ public static boolean usePeopleFiltering(Context context) {
+ if (sUsePeopleFiltering == null) {
+ sUsePeopleFiltering = context.getResources().getBoolean(
+ R.bool.config_usePeopleFiltering);
+ }
+
+ return sUsePeopleFiltering;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java
index 727e245..aacb2dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java
@@ -16,10 +16,15 @@
package com.android.systemui.statusbar.notification.collection;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
+
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Person;
+import android.content.Context;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.SnoozeCriterion;
@@ -30,6 +35,7 @@
import com.android.systemui.Dependency;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.notification.NotificationFilter;
+import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -44,6 +50,7 @@
* The list of currently displaying notifications.
*/
public class NotificationData {
+ private static final String TAG = "NotificationData";
private final NotificationFilter mNotificationFilter = Dependency.get(NotificationFilter.class);
@@ -64,6 +71,11 @@
private RankingMap mRankingMap;
private final Ranking mTmpRanking = new Ranking();
+ private final boolean mUsePeopleFiltering;
+
+ public NotificationData(Context context) {
+ mUsePeopleFiltering = NotificationUtils.usePeopleFiltering(context);
+ }
public void setHeadsUpManager(HeadsUpManager headsUpManager) {
mHeadsUpManager = headsUpManager;
@@ -72,52 +84,25 @@
@VisibleForTesting
protected final Comparator<NotificationEntry> mRankingComparator =
new Comparator<NotificationEntry>() {
- private final Ranking mRankingA = new Ranking();
- private final Ranking mRankingB = new Ranking();
-
@Override
public int compare(NotificationEntry a, NotificationEntry b) {
final StatusBarNotification na = a.notification;
final StatusBarNotification nb = b.notification;
- int aImportance = NotificationManager.IMPORTANCE_DEFAULT;
- int bImportance = NotificationManager.IMPORTANCE_DEFAULT;
- int aRank = 0;
- int bRank = 0;
+ int aRank = getRank(a.key);
+ int bRank = getRank(b.key);
- if (mRankingMap != null) {
- // RankingMap as received from NoMan
- getRanking(a.key, mRankingA);
- getRanking(b.key, mRankingB);
- aImportance = mRankingA.getImportance();
- bImportance = mRankingB.getImportance();
- aRank = mRankingA.getRank();
- bRank = mRankingB.getRank();
- }
+ boolean aMedia = isImportantMedia(a);
+ boolean bMedia = isImportantMedia(b);
- String mediaNotification = getMediaManager().getMediaNotificationKey();
+ boolean aSystemMax = isSystemMax(a);
+ boolean bSystemMax = isSystemMax(b);
- // IMPORTANCE_MIN media streams are allowed to drift to the bottom
- final boolean aMedia = a.key.equals(mediaNotification)
- && aImportance > NotificationManager.IMPORTANCE_MIN;
- final boolean bMedia = b.key.equals(mediaNotification)
- && bImportance > NotificationManager.IMPORTANCE_MIN;
+ boolean aHeadsUp = a.isRowHeadsUp();
+ boolean bHeadsUp = b.isRowHeadsUp();
- boolean aSystemMax = aImportance >= NotificationManager.IMPORTANCE_HIGH
- && isSystemNotification(na);
- boolean bSystemMax = bImportance >= NotificationManager.IMPORTANCE_HIGH
- && isSystemNotification(nb);
-
-
- boolean aHeadsUp = a.getRow().isHeadsUp();
- boolean bHeadsUp = b.getRow().isHeadsUp();
-
- // HACK: This should really go elsewhere, but it's currently not straightforward to
- // extract the comparison code and we're guaranteed to touch every element, so this is
- // the best place to set the buckets for the moment.
- a.setIsTopBucket(aHeadsUp || aMedia || aSystemMax || a.isHighPriority());
- b.setIsTopBucket(bHeadsUp || bMedia || bSystemMax || b.isHighPriority());
-
- if (aHeadsUp != bHeadsUp) {
+ if (mUsePeopleFiltering && a.hasAssociatedPeople() != b.hasAssociatedPeople()) {
+ return a.hasAssociatedPeople() ? -1 : 1;
+ } else if (aHeadsUp != bHeadsUp) {
return aHeadsUp ? -1 : 1;
} else if (aHeadsUp) {
// Provide consistent ranking with headsUpManager
@@ -317,14 +302,6 @@
return Ranking.VISIBILITY_NO_OVERRIDE;
}
- public int getImportance(String key) {
- if (mRankingMap != null) {
- getRanking(key, mTmpRanking);
- return mTmpRanking.getImportance();
- }
- return NotificationManager.IMPORTANCE_UNSPECIFIED;
- }
-
public List<SnoozeCriterion> getSnoozeCriteria(String key) {
if (mRankingMap != null) {
getRanking(key, mTmpRanking);
@@ -349,6 +326,22 @@
return 0;
}
+ private boolean isImportantMedia(NotificationEntry e) {
+ int importance = e.ranking().getImportance();
+ boolean media = e.key.equals(getMediaManager().getMediaNotificationKey())
+ && importance > NotificationManager.IMPORTANCE_MIN;
+
+ return media;
+ }
+
+ private boolean isSystemMax(NotificationEntry e) {
+ int importance = e.ranking().getImportance();
+ boolean sys = importance >= NotificationManager.IMPORTANCE_HIGH
+ && isSystemNotification(e.notification);
+
+ return sys;
+ }
+
public boolean shouldHide(String key) {
if (mRankingMap != null) {
getRanking(key, mTmpRanking);
@@ -414,13 +407,37 @@
}
}
- if (mSortedAndFiltered.size() == 1) {
- // HACK: We need the comparator to run on all children in order to set the
- // isHighPriority field. If there is only one child, then the comparison won't be run,
- // so we have to trigger it manually. Get rid of this code as soon as possible.
- mRankingComparator.compare(mSortedAndFiltered.get(0), mSortedAndFiltered.get(0));
+ Collections.sort(mSortedAndFiltered, mRankingComparator);
+
+ int bucket = BUCKET_PEOPLE;
+ for (NotificationEntry e : mSortedAndFiltered) {
+ assignBucketForEntry(e);
+ if (e.getBucket() < bucket) {
+ android.util.Log.wtf(TAG, "Detected non-contiguous bucket!");
+ }
+ bucket = e.getBucket();
+ }
+ }
+
+ private void assignBucketForEntry(NotificationEntry e) {
+ boolean isHeadsUp = e.isRowHeadsUp();
+ boolean isMedia = isImportantMedia(e);
+ boolean isSystemMax = isSystemMax(e);
+
+ setBucket(e, isHeadsUp, isMedia, isSystemMax);
+ }
+
+ private void setBucket(
+ NotificationEntry e,
+ boolean isHeadsUp,
+ boolean isMedia,
+ boolean isSystemMax) {
+ if (mUsePeopleFiltering && e.hasAssociatedPeople()) {
+ e.setBucket(BUCKET_PEOPLE);
+ } else if (isHeadsUp || isMedia || isSystemMax || e.isHighPriority()) {
+ e.setBucket(BUCKET_ALERTING);
} else {
- Collections.sort(mSortedAndFiltered, mRankingComparator);
+ e.setBucket(BUCKET_SILENT);
}
}
@@ -466,6 +483,19 @@
}
/**
+ * Get the current set of buckets for notification entries, as defined here
+ */
+ public static int[] getNotificationBuckets(Context context) {
+ if (NotificationUtils.usePeopleFiltering(context)) {
+ return new int[]{BUCKET_PEOPLE, BUCKET_ALERTING, BUCKET_SILENT};
+ } else if (NotificationUtils.useNewInterruptionModel(context)) {
+ return new int[]{BUCKET_ALERTING, BUCKET_SILENT};
+ } else {
+ return new int[]{BUCKET_ALERTING};
+ }
+ }
+
+ /**
* Provides access to keyguard state and user settings dependent data.
*/
public interface KeyguardEnvironment {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 71cdcf7..c3211e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -21,6 +21,7 @@
import static android.app.Notification.CATEGORY_EVENT;
import static android.app.Notification.CATEGORY_MESSAGE;
import static android.app.Notification.CATEGORY_REMINDER;
+import static android.app.Notification.EXTRA_MESSAGES;
import static android.app.Notification.FLAG_BUBBLE;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE;
@@ -29,6 +30,8 @@
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING;
+
import android.annotation.NonNull;
import android.app.Notification;
import android.app.Notification.MessagingStyle.Message;
@@ -38,6 +41,7 @@
import android.content.Context;
import android.graphics.drawable.Icon;
import android.os.Bundle;
+import android.os.Parcelable;
import android.os.SystemClock;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.SnoozeCriterion;
@@ -58,6 +62,7 @@
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag;
import com.android.systemui.statusbar.notification.row.NotificationGuts;
+import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
import java.util.ArrayList;
import java.util.List;
@@ -99,6 +104,7 @@
public int targetSdk;
private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET;
public CharSequence remoteInputText;
+ private final List<Person> mAssociatedPeople = new ArrayList<>();
/**
* If {@link android.app.RemoteInput#getEditChoicesBeforeSending} is enabled, and the user is
@@ -135,17 +141,22 @@
private boolean hasSentReply;
/**
+ * Whether this notification has changed in visual appearance since the previous post.
+ * New notifications are interruptive by default.
+ */
+ public boolean isVisuallyInterruptive;
+
+ /**
* Whether this notification is shown to the user as a high priority notification: visible on
* the lock screen/status bar and in the top section in the shade.
*/
private boolean mHighPriority;
- private boolean mIsTopBucket;
-
private boolean mSensitive = true;
private Runnable mOnSensitiveChangedListener;
private boolean mAutoHeadsUp;
private boolean mPulseSupressed;
+ private int mBucket = BUCKET_ALERTING;
public NotificationEntry(
@NonNull StatusBarNotification sbn,
@@ -173,11 +184,12 @@
* TODO: Make this package-private
*/
public void setNotification(StatusBarNotification sbn) {
- if (!sbn.getKey().equals(key)) {
+ if (sbn.getKey() != null && key != null && !sbn.getKey().equals(key)) {
throw new IllegalArgumentException("New key " + sbn.getKey()
+ " doesn't match existing key " + key);
}
notification = sbn;
+ updatePeopleList();
}
/**
@@ -199,6 +211,7 @@
+ " doesn't match existing key " + key);
}
mRanking = ranking;
+ isVisuallyInterruptive = ranking.visuallyInterruptive();
}
public NotificationChannel getChannel() {
@@ -238,6 +251,7 @@
return mRanking.canBubble();
}
+
public @NonNull List<Notification.Action> getSmartActions() {
return mRanking.getSmartActions();
}
@@ -263,22 +277,41 @@
this.mHighPriority = highPriority;
}
- /**
- * @return True if the notif should appear in the "top" or "important" section of notifications
- * (as opposed to the "bottom" or "silent" section). This is usually the same as
- * {@link #isHighPriority()}, but there are certain exceptions, such as media notifs.
- */
- public boolean isTopBucket() {
- return mIsTopBucket;
- }
- public void setIsTopBucket(boolean isTopBucket) {
- mIsTopBucket = isTopBucket;
- }
-
public boolean isBubble() {
return (notification.getNotification().flags & FLAG_BUBBLE) != 0;
}
+ private void updatePeopleList() {
+ mAssociatedPeople.clear();
+
+ Bundle extras = notification.getNotification().extras;
+ if (extras == null) {
+ return;
+ }
+
+ List<Person> p = extras.getParcelableArrayList(Notification.EXTRA_PEOPLE_LIST);
+
+ if (p != null) {
+ mAssociatedPeople.addAll(p);
+ }
+
+ if (Notification.MessagingStyle.class.equals(
+ notification.getNotification().getNotificationStyle())) {
+ final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
+ if (!ArrayUtils.isEmpty(messages)) {
+ for (Notification.MessagingStyle.Message message :
+ Notification.MessagingStyle.Message
+ .getMessagesFromBundleArray(messages)) {
+ mAssociatedPeople.add(message.getSenderPerson());
+ }
+ }
+ }
+ }
+
+ boolean hasAssociatedPeople() {
+ return mAssociatedPeople.size() > 0;
+ }
+
/**
* Returns the data needed for a bubble for this notification, if it exists.
*/
@@ -295,6 +328,15 @@
}
}
+ @NotificationSectionsManager.PriorityBucket
+ public int getBucket() {
+ return mBucket;
+ }
+
+ public void setBucket(@NotificationSectionsManager.PriorityBucket int bucket) {
+ mBucket = bucket;
+ }
+
public ExpandableNotificationRow getRow() {
return row;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
index 4221846..ec0c634 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
@@ -16,11 +16,10 @@
package com.android.systemui.statusbar.notification.stack;
-import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.NUM_SECTIONS;
-
-
+import android.content.Context;
import android.util.MathUtils;
+import com.android.systemui.statusbar.notification.collection.NotificationData;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -51,11 +50,14 @@
private float mAppearFraction;
@Inject
- NotificationRoundnessManager(KeyguardBypassController keyguardBypassController) {
- mFirstInSectionViews = new ActivatableNotificationView[NUM_SECTIONS];
- mLastInSectionViews = new ActivatableNotificationView[NUM_SECTIONS];
- mTmpFirstInSectionViews = new ActivatableNotificationView[NUM_SECTIONS];
- mTmpLastInSectionViews = new ActivatableNotificationView[NUM_SECTIONS];
+ NotificationRoundnessManager(
+ KeyguardBypassController keyguardBypassController,
+ Context context) {
+ int numberOfSections = NotificationData.getNotificationBuckets(context).length;
+ mFirstInSectionViews = new ActivatableNotificationView[numberOfSections];
+ mLastInSectionViews = new ActivatableNotificationView[numberOfSections];
+ mTmpFirstInSectionViews = new ActivatableNotificationView[numberOfSections];
+ mTmpLastInSectionViews = new ActivatableNotificationView[numberOfSections];
mBypassController = keyguardBypassController;
}
@@ -157,7 +159,7 @@
public void updateRoundedChildren(NotificationSection[] sections) {
boolean anyChanged = false;
- for (int i = 0; i < NUM_SECTIONS; i++) {
+ for (int i = 0; i < sections.length; i++) {
mTmpFirstInSectionViews[i] = mFirstInSectionViews[i];
mTmpLastInSectionViews[i] = mLastInSectionViews[i];
mFirstInSectionViews[i] = sections[i].getFirstVisibleChild();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
index f39ed2e..9d456ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
@@ -33,6 +33,7 @@
* bounds change.
*/
class NotificationSection {
+ private @NotificationSectionsManager.PriorityBucket int mBucket;
private View mOwningView;
private Rect mBounds = new Rect();
private Rect mCurrentBounds = new Rect(-1, -1, -1, -1);
@@ -43,8 +44,9 @@
private ActivatableNotificationView mFirstVisibleChild;
private ActivatableNotificationView mLastVisibleChild;
- NotificationSection(View owningView) {
+ NotificationSection(View owningView, @NotificationSectionsManager.PriorityBucket int bucket) {
mOwningView = owningView;
+ mBucket = bucket;
}
public void cancelAnimators() {
@@ -72,6 +74,11 @@
return mBottomAnimator != null || mTopAnimator != null;
}
+ @NotificationSectionsManager.PriorityBucket
+ public int getBucket() {
+ return mBucket;
+ }
+
public void startBackgroundAnimation(boolean animateTop, boolean animateBottom) {
// Left and right bounds are always applied immediately.
mCurrentBounds.left = mBounds.left;
@@ -199,12 +206,16 @@
return mLastVisibleChild;
}
- public void setFirstVisibleChild(ActivatableNotificationView child) {
+ public boolean setFirstVisibleChild(ActivatableNotificationView child) {
+ boolean changed = mFirstVisibleChild != child;
mFirstVisibleChild = child;
+ return changed;
}
- public void setLastVisibleChild(ActivatableNotificationView child) {
+ public boolean setLastVisibleChild(ActivatableNotificationView child) {
+ boolean changed = mLastVisibleChild != child;
mLastVisibleChild = child;
+ return changed;
}
public void resetCurrentBounds() {
@@ -291,5 +302,4 @@
mBounds.bottom = bottom;
return bottom;
}
-
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
index d119fb79..d0444ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
@@ -18,6 +18,10 @@
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Intent;
import android.provider.Settings;
@@ -34,23 +38,31 @@
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
+import java.lang.annotation.Retention;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Manages the boundaries of the two notification sections (high priority and low priority). Also
* shows/hides the headers for those sections where appropriate.
*
* TODO: Move remaining sections logic from NSSL into this class.
*/
-class NotificationSectionsManager implements StackScrollAlgorithm.SectionProvider {
+public class NotificationSectionsManager implements StackScrollAlgorithm.SectionProvider {
+
+ private static final String TAG = "NotifSectionsManager";
+ private static final boolean DEBUG = false;
+
private final NotificationStackScrollLayout mParent;
private final ActivityStarter mActivityStarter;
private final StatusBarStateController mStatusBarStateController;
private final ConfigurationController mConfigurationController;
- private final boolean mUseMultipleSections;
+ private final int mNumberOfSections;
private boolean mInitialized = false;
private SectionHeaderView mGentleHeader;
private boolean mGentleHeaderVisible = false;
- @Nullable private ExpandableNotificationRow mFirstGentleNotif;
+
@Nullable private View.OnClickListener mOnClearGentleNotifsClickListener;
NotificationSectionsManager(
@@ -58,12 +70,21 @@
ActivityStarter activityStarter,
StatusBarStateController statusBarStateController,
ConfigurationController configurationController,
- boolean useMultipleSections) {
+ int numberOfSections) {
mParent = parent;
mActivityStarter = activityStarter;
mStatusBarStateController = statusBarStateController;
mConfigurationController = configurationController;
- mUseMultipleSections = useMultipleSections;
+ mNumberOfSections = numberOfSections;
+ }
+
+ NotificationSection[] createSectionsForBuckets(int[] buckets) {
+ NotificationSection[] sections = new NotificationSection[buckets.length];
+ for (int i = 0; i < buckets.length; i++) {
+ sections[i] = new NotificationSection(mParent, buckets[i] /* bucket */);
+ }
+
+ return sections;
}
/** Must be called before use. */
@@ -111,8 +132,38 @@
}
@Override
- public boolean beginsSection(View view) {
- return view == getFirstLowPriorityChild();
+ public boolean beginsSection(@NonNull View view, @Nullable View previous) {
+ boolean begin = false;
+ if (view instanceof ExpandableNotificationRow) {
+ if (previous instanceof ExpandableNotificationRow) {
+ // If we're drawing the first non-person notification, break out a section
+ ExpandableNotificationRow curr = (ExpandableNotificationRow) view;
+ ExpandableNotificationRow prev = (ExpandableNotificationRow) previous;
+
+ begin = curr.getEntry().getBucket() != prev.getEntry().getBucket();
+ }
+ }
+
+ if (!begin) {
+ begin = view == mGentleHeader;
+ }
+
+ return begin;
+ }
+
+ private boolean isUsingMultipleSections() {
+ return mNumberOfSections > 1;
+ }
+
+ private @PriorityBucket int getBucket(ActivatableNotificationView view)
+ throws IllegalArgumentException {
+ if (view instanceof ExpandableNotificationRow) {
+ return ((ExpandableNotificationRow) view).getEntry().getBucket();
+ } else if (view == mGentleHeader) {
+ return BUCKET_SILENT;
+ }
+
+ throw new IllegalArgumentException("I don't know how to find a bucket for this view :(");
}
/**
@@ -120,11 +171,10 @@
* bookkeeping and adds/moves/removes section headers if appropriate.
*/
void updateSectionBoundaries() {
- if (!mUseMultipleSections) {
+ if (!isUsingMultipleSections()) {
return;
}
- mFirstGentleNotif = null;
int firstGentleNotifIndex = -1;
final int n = mParent.getChildCount();
@@ -133,9 +183,8 @@
if (child instanceof ExpandableNotificationRow
&& child.getVisibility() != View.GONE) {
ExpandableNotificationRow row = (ExpandableNotificationRow) child;
- if (!row.getEntry().isTopBucket()) {
+ if (row.getEntry().getBucket() == BUCKET_SILENT) {
firstGentleNotifIndex = i;
- mFirstGentleNotif = row;
break;
}
}
@@ -184,80 +233,75 @@
}
/**
- * Updates the boundaries (as tracked by their first and last views) of the high and low
- * priority sections.
+ * Updates the boundaries (as tracked by their first and last views) of the priority sections.
*
* @return {@code true} If the last view in the top section changed (so we need to animate).
*/
- boolean updateFirstAndLastViewsInSections(
- final NotificationSection highPrioritySection,
- final NotificationSection lowPrioritySection,
- ActivatableNotificationView firstChild,
- ActivatableNotificationView lastChild) {
- if (mUseMultipleSections) {
- ActivatableNotificationView previousLastHighPriorityChild =
- highPrioritySection.getLastVisibleChild();
- ActivatableNotificationView previousFirstLowPriorityChild =
- lowPrioritySection.getFirstVisibleChild();
- ActivatableNotificationView lastHighPriorityChild = getLastHighPriorityChild();
- ActivatableNotificationView firstLowPriorityChild = getFirstLowPriorityChild();
- if (lastHighPriorityChild != null && firstLowPriorityChild != null) {
- highPrioritySection.setFirstVisibleChild(firstChild);
- highPrioritySection.setLastVisibleChild(lastHighPriorityChild);
- lowPrioritySection.setFirstVisibleChild(firstLowPriorityChild);
- lowPrioritySection.setLastVisibleChild(lastChild);
- } else if (lastHighPriorityChild != null) {
- highPrioritySection.setFirstVisibleChild(firstChild);
- highPrioritySection.setLastVisibleChild(lastChild);
- lowPrioritySection.setFirstVisibleChild(null);
- lowPrioritySection.setLastVisibleChild(null);
- } else {
- highPrioritySection.setFirstVisibleChild(null);
- highPrioritySection.setLastVisibleChild(null);
- lowPrioritySection.setFirstVisibleChild(firstChild);
- lowPrioritySection.setLastVisibleChild(lastChild);
+ boolean updateFirstAndLastViewsForAllSections(
+ NotificationSection[] sections,
+ List<ActivatableNotificationView> children) {
+
+ if (sections.length <= 0 || children.size() <= 0) {
+ for (NotificationSection s : sections) {
+ s.setFirstVisibleChild(null);
+ s.setLastVisibleChild(null);
}
- return lastHighPriorityChild != previousLastHighPriorityChild
- || firstLowPriorityChild != previousFirstLowPriorityChild;
- } else {
- highPrioritySection.setFirstVisibleChild(firstChild);
- highPrioritySection.setLastVisibleChild(lastChild);
return false;
}
+
+ boolean changed = false;
+ ArrayList<ActivatableNotificationView> viewsInBucket = new ArrayList<>();
+ for (NotificationSection s : sections) {
+ int filter = s.getBucket();
+ viewsInBucket.clear();
+
+ //TODO: do this in a single pass, and more better
+ for (ActivatableNotificationView v : children) {
+ if (getBucket(v) == filter) {
+ viewsInBucket.add(v);
+ }
+
+ if (viewsInBucket.size() >= 1) {
+ changed |= s.setFirstVisibleChild(viewsInBucket.get(0));
+ changed |= s.setLastVisibleChild(viewsInBucket.get(viewsInBucket.size() - 1));
+ } else {
+ changed |= s.setFirstVisibleChild(null);
+ changed |= s.setLastVisibleChild(null);
+ }
+ }
+ }
+
+ if (DEBUG) {
+ logSections(sections);
+ }
+
+ return changed;
}
+ private void logSections(NotificationSection[] sections) {
+ for (int i = 0; i < sections.length; i++) {
+ NotificationSection s = sections[i];
+ ActivatableNotificationView first = s.getFirstVisibleChild();
+ String fs = first == null ? "(null)"
+ : (first instanceof ExpandableNotificationRow)
+ ? ((ExpandableNotificationRow) first).getEntry().key
+ : Integer.toHexString(System.identityHashCode(first));
+ ActivatableNotificationView last = s.getLastVisibleChild();
+ String ls = last == null ? "(null)"
+ : (last instanceof ExpandableNotificationRow)
+ ? ((ExpandableNotificationRow) last).getEntry().key
+ : Integer.toHexString(System.identityHashCode(last));
+ android.util.Log.d(TAG, "updateSections: f=" + fs + " s=" + i);
+ android.util.Log.d(TAG, "updateSections: l=" + ls + " s=" + i);
+ }
+ }
+
+
@VisibleForTesting
SectionHeaderView getGentleHeaderView() {
return mGentleHeader;
}
- @Nullable
- private ActivatableNotificationView getFirstLowPriorityChild() {
- if (mGentleHeaderVisible) {
- return mGentleHeader;
- } else {
- return mFirstGentleNotif;
- }
- }
-
- @Nullable
- private ActivatableNotificationView getLastHighPriorityChild() {
- ActivatableNotificationView lastChildBeforeGap = null;
- int childCount = mParent.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = mParent.getChildAt(i);
- if (child.getVisibility() != View.GONE && child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
- if (!row.getEntry().isTopBucket()) {
- break;
- } else {
- lastChildBeforeGap = row;
- }
- }
- }
- return lastChildBeforeGap;
- }
-
private final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
@Override
public void onLocaleListChanged() {
@@ -279,4 +323,20 @@
mOnClearGentleNotifsClickListener.onClick(v);
}
}
+
+ /**
+ * For now, declare the available notification buckets (sections) here so that other
+ * presentation code can decide what to do based on an entry's buckets
+ *
+ */
+ @Retention(SOURCE)
+ @IntDef(prefix = { "BUCKET_" }, value = {
+ BUCKET_PEOPLE,
+ BUCKET_ALERTING,
+ BUCKET_SILENT
+ })
+ public @interface PriorityBucket {}
+ public static final int BUCKET_PEOPLE = 0;
+ public static final int BUCKET_ALERTING = 1;
+ public static final int BUCKET_SILENT = 2;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 1c9b225..a67018e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -18,6 +18,7 @@
import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
import static com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.ANCHOR_SCROLLING;
import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE;
import static com.android.systemui.statusbar.phone.NotificationIconAreaController.HIGH_PRIORITY;
@@ -112,6 +113,7 @@
import com.android.systemui.statusbar.notification.ShadeViewRefactor;
import com.android.systemui.statusbar.notification.ShadeViewRefactor.RefactorComponent;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationData;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
@@ -172,7 +174,6 @@
* Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
*/
private static final int INVALID_POINTER = -1;
- static final int NUM_SECTIONS = 2;
/**
* The distance in pixels between sections when the sections are directly adjacent (no visible
* gap is drawn between them). In this case we don't want to round their corners.
@@ -351,7 +352,7 @@
return true;
}
};
- private NotificationSection[] mSections = new NotificationSection[NUM_SECTIONS];
+ private NotificationSection[] mSections;
private boolean mAnimateNextBackgroundTop;
private boolean mAnimateNextBackgroundBottom;
private boolean mAnimateNextSectionBoundsChange;
@@ -522,9 +523,6 @@
mAllowLongPress = allowLongPress;
- for (int i = 0; i < NUM_SECTIONS; i++) {
- mSections[i] = new NotificationSection(this);
- }
mRoundnessManager = notificationRoundnessManager;
mHeadsUpManager = headsUpManager;
@@ -533,19 +531,21 @@
mKeyguardBypassController = keyguardBypassController;
mFalsingManager = falsingManager;
+ int[] buckets = NotificationData.getNotificationBuckets(context);
mSectionsManager =
new NotificationSectionsManager(
this,
activityStarter,
statusBarStateController,
configurationController,
- NotificationUtils.useNewInterruptionModel(context));
+ buckets.length);
mSectionsManager.initialize(LayoutInflater.from(context));
mSectionsManager.setOnClearGentleNotifsClickListener(v -> {
// Leave the shade open if there will be other notifs left over to clear
final boolean closeShade = !hasActiveClearableNotifications(ROWS_HIGH_PRIORITY);
clearNotifications(ROWS_GENTLE, closeShade);
});
+ mSections = mSectionsManager.createSectionsForBuckets(buckets);
mAmbientState = new AmbientState(context, mSectionsManager, mHeadsUpManager);
mBgColor = context.getColor(R.color.notification_shade_background_color);
@@ -773,7 +773,7 @@
protected void onDraw(Canvas canvas) {
if (mShouldDrawNotificationBackground
&& (mSections[0].getCurrentBounds().top
- < mSections[NUM_SECTIONS - 1].getCurrentBounds().bottom
+ < mSections[mSections.length - 1].getCurrentBounds().bottom
|| mAmbientState.isDozing())) {
drawBackground(canvas);
} else if (mInHeadsUpPinnedMode || mHeadsUpAnimatingAway) {
@@ -819,7 +819,7 @@
int lockScreenLeft = mSidePaddings;
int lockScreenRight = getWidth() - mSidePaddings;
int lockScreenTop = mSections[0].getCurrentBounds().top;
- int lockScreenBottom = mSections[NUM_SECTIONS - 1].getCurrentBounds().bottom;
+ int lockScreenBottom = mSections[mSections.length - 1].getCurrentBounds().bottom;
int hiddenLeft = getWidth() / 2;
int hiddenTop = mTopPadding;
@@ -2636,6 +2636,21 @@
return null;
}
+ //TODO: We shouldn't have to generate this list every time
+ private List<ActivatableNotificationView> getChildrenWithBackground() {
+ ArrayList<ActivatableNotificationView> children = new ArrayList<>();
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ if (child.getVisibility() != View.GONE && child instanceof ActivatableNotificationView
+ && child != mShelf) {
+ children.add((ActivatableNotificationView) child);
+ }
+ }
+
+ return children;
+ }
+
/**
* Fling the scroll view
*
@@ -3198,8 +3213,8 @@
ActivatableNotificationView firstChild = getFirstChildWithBackground();
ActivatableNotificationView lastChild = getLastChildWithBackground();
- boolean sectionViewsChanged = mSectionsManager.updateFirstAndLastViewsInSections(
- mSections[0], mSections[1], firstChild, lastChild);
+ boolean sectionViewsChanged = mSectionsManager.updateFirstAndLastViewsForAllSections(
+ mSections, getChildrenWithBackground());
if (mAnimationsEnabled && mIsExpanded) {
mAnimateNextBackgroundTop = firstChild != previousFirstChild;
@@ -5780,7 +5795,7 @@
currentIndex++;
boolean beforeSpeedBump;
if (mHighPriorityBeforeSpeedBump) {
- beforeSpeedBump = row.getEntry().isTopBucket();
+ beforeSpeedBump = row.getEntry().getBucket() < BUCKET_SILENT;
} else {
beforeSpeedBump = !row.getEntry().isAmbient();
}
@@ -5838,9 +5853,9 @@
case ROWS_ALL:
return true;
case ROWS_HIGH_PRIORITY:
- return row.getEntry().isTopBucket();
+ return row.getEntry().getBucket() < BUCKET_SILENT;
case ROWS_GENTLE:
- return !row.getEntry().isTopBucket();
+ return row.getEntry().getBucket() == BUCKET_SILENT;
default:
throw new IllegalArgumentException("Unknown selection: " + selection);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index ef80484..4b61064 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.stack;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
import android.util.Log;
@@ -411,8 +413,10 @@
float currentYPosition,
boolean reverse) {
ExpandableView child = algorithmState.visibleChildren.get(i);
+ ExpandableView previousChild = i > 0 ? algorithmState.visibleChildren.get(i - 1) : null;
final boolean applyGapHeight =
- childNeedsGapHeight(ambientState.getSectionProvider(), algorithmState, i, child);
+ childNeedsGapHeight(
+ ambientState.getSectionProvider(), algorithmState, i, child, previousChild);
ExpandableViewState childViewState = child.getViewState();
childViewState.location = ExpandableViewState.LOCATION_UNKNOWN;
@@ -477,8 +481,11 @@
SectionProvider sectionProvider,
StackScrollAlgorithmState algorithmState,
int visibleIndex,
- View child) {
- boolean needsGapHeight = sectionProvider.beginsSection(child) && visibleIndex > 0;
+ View child,
+ View previousChild) {
+
+ boolean needsGapHeight = sectionProvider.beginsSection(child, previousChild)
+ && visibleIndex > 0;
if (ANCHOR_SCROLLING) {
needsGapHeight &= visibleIndex != algorithmState.anchorViewIndex;
}
@@ -749,6 +756,6 @@
* True if this view starts a new "section" of notifications, such as the gentle
* notifications section. False if sections are not enabled.
*/
- boolean beginsSection(View view);
+ boolean beginsSection(@NonNull View view, @Nullable View previous);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
index 4cd3ad2..575b559 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
@@ -18,6 +18,7 @@
import static android.view.Display.INVALID_DISPLAY;
import android.content.Context;
+import android.content.pm.ParceledListSlice;
import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.graphics.Point;
@@ -35,6 +36,8 @@
import android.util.MathUtils;
import android.util.StatsLog;
import android.view.Gravity;
+import android.view.IPinnedStackController;
+import android.view.IPinnedStackListener;
import android.view.ISystemGestureExclusionListener;
import android.view.InputChannel;
import android.view.InputDevice;
@@ -54,7 +57,6 @@
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.model.SysUiState;
import com.android.systemui.recents.OverviewProxyService;
-import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.WindowManagerWrapper;
@@ -70,13 +72,35 @@
private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt(
"gestures.back_timeout", 250);
- private final PinnedStackListener mImeChangedListener = new PinnedStackListener() {
+ private final IPinnedStackListener.Stub mImeChangedListener = new IPinnedStackListener.Stub() {
+ @Override
+ public void onListenerRegistered(IPinnedStackController controller) {
+ }
+
@Override
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
// No need to thread jump, assignments are atomic
mImeHeight = imeVisible ? imeHeight : 0;
// TODO: Probably cancel any existing gesture
}
+
+ @Override
+ public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {
+ }
+
+ @Override
+ public void onMinimizedStateChanged(boolean isMinimized) {
+ }
+
+ @Override
+ public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds,
+ Rect animatingBounds, boolean fromImeAdjustment, boolean fromShelfAdjustment,
+ int displayRotation) {
+ }
+
+ @Override
+ public void onActionsChanged(ParceledListSlice actions) {
+ }
};
private ISystemGestureExclusionListener mGestureExclusionListener =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
index f7bb97b..00b764b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -24,7 +24,7 @@
import com.android.systemui.Dependency
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.util.Assert
-import com.android.systemui.util.AsyncSensorManager
+import com.android.systemui.util.sensors.AsyncSensorManager
class KeyguardLiftController constructor(
private val statusBarStateController: StatusBarStateController,
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 a79ecd9..ea113df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -102,6 +102,7 @@
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -177,12 +178,22 @@
@VisibleForTesting
final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback =
new KeyguardUpdateMonitorCallback() {
+
+ @Override
+ public void onBiometricAuthenticated(int userId,
+ BiometricSourceType biometricSourceType) {
+ if (mFirstBypassAttempt && mUpdateMonitor.isUnlockingWithBiometricAllowed()) {
+ mDelayShowingKeyguardStatusBar = true;
+ }
+ }
+
@Override
public void onBiometricRunningStateChanged(boolean running,
BiometricSourceType biometricSourceType) {
boolean keyguardOrShadeLocked = mBarState == StatusBarState.KEYGUARD
|| mBarState == StatusBarState.SHADE_LOCKED;
- if (!running && mFirstBypassAttempt && keyguardOrShadeLocked && !mDozing) {
+ if (!running && mFirstBypassAttempt && keyguardOrShadeLocked && !mDozing
+ && !mDelayShowingKeyguardStatusBar) {
mFirstBypassAttempt = false;
animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
}
@@ -191,6 +202,17 @@
@Override
public void onFinishedGoingToSleep(int why) {
mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
+ mDelayShowingKeyguardStatusBar = false;
+ }
+ };
+ private final KeyguardMonitor.Callback mKeyguardMonitorCallback =
+ new KeyguardMonitor.Callback() {
+ @Override
+ public void onKeyguardFadingAwayChanged() {
+ if (!mKeyguardMonitor.isKeyguardFadingAway()) {
+ mFirstBypassAttempt = false;
+ mDelayShowingKeyguardStatusBar = false;
+ }
}
};
@@ -413,7 +435,17 @@
private boolean mShowingKeyguardHeadsUp;
private boolean mAllowExpandForSmallExpansion;
private Runnable mExpandAfterLayoutRunnable;
+
+ /**
+ * If face auth with bypass is running for the first time after you turn on the screen.
+ * (From aod or screen off)
+ */
private boolean mFirstBypassAttempt;
+ /**
+ * If auth happens successfully during {@code mFirstBypassAttempt}, and we should wait until
+ * the keyguard is dismissed to show the status bar.
+ */
+ private boolean mDelayShowingKeyguardStatusBar;
private PluginManager mPluginManager;
private FrameLayout mPluginFrame;
@@ -450,6 +482,7 @@
mKeyguardBypassController = bypassController;
mUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
+ mKeyguardMonitor.addCallback(mKeyguardMonitorCallback);
dynamicPrivacyController.addListener(this);
mBottomAreaShadeAlphaAnimator = ValueAnimator.ofFloat(1f, 0);
@@ -2391,7 +2424,8 @@
* mKeyguardStatusBarAnimateAlpha;
newAlpha *= 1.0f - mKeyguardHeadsUpShowingAmount;
mKeyguardStatusBar.setAlpha(newAlpha);
- boolean hideForBypass = mFirstBypassAttempt && mUpdateMonitor.shouldListenForFace();
+ boolean hideForBypass = mFirstBypassAttempt && mUpdateMonitor.shouldListenForFace()
+ || mDelayShowingKeyguardStatusBar;
mKeyguardStatusBar.setVisibility(newAlpha != 0f && !mDozing && !hideForBypass
? VISIBLE : INVISIBLE);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index f853b63..8c95b84 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -503,8 +503,8 @@
* device is dozing when the light sensor is on.
*/
public void setAodFrontScrimAlpha(float alpha) {
- if (((mState == ScrimState.AOD && mDozeParameters.getAlwaysOn())
- || mState == ScrimState.PULSING) && mInFrontAlpha != alpha) {
+ if (mState == ScrimState.AOD && mDozeParameters.getAlwaysOn()
+ && mInFrontAlpha != alpha) {
mInFrontAlpha = alpha;
updateScrims();
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/ProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/ProximitySensor.java
deleted file mode 100644
index a905eba..0000000
--- a/packages/SystemUI/src/com/android/systemui/util/ProximitySensor.java
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Copyright (C) 2019 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.util;
-
-import android.content.Context;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.util.Log;
-
-import com.android.systemui.R;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.inject.Inject;
-
-/**
- * Simple wrapper around SensorManager customized for the Proximity sensor.
- */
-public class ProximitySensor {
- private static final String TAG = "ProxSensor";
- private static final boolean DEBUG = false;
-
- private final Sensor mSensor;
- private final AsyncSensorManager mSensorManager;
- private final boolean mUsingBrightnessSensor;
- private final float mMaxRange;
-
- private SensorEventListener mSensorEventListener = new SensorEventListener() {
- @Override
- public synchronized void onSensorChanged(SensorEvent event) {
- onSensorEvent(event);
- }
-
- @Override
- public void onAccuracyChanged(Sensor sensor, int accuracy) {
- }
- };
- private boolean mNear;
- private List<ProximitySensorListener> mListeners = new ArrayList<>();
- private String mTag = null;
-
- @Inject
- public ProximitySensor(Context context, AsyncSensorManager sensorManager) {
- mSensorManager = sensorManager;
- Sensor sensor = findBrightnessSensor(context, sensorManager);
-
- if (sensor == null) {
- mUsingBrightnessSensor = false;
- sensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
- } else {
- mUsingBrightnessSensor = true;
- }
- mSensor = sensor;
- if (mSensor != null) {
- mMaxRange = mSensor.getMaximumRange();
- } else {
- mMaxRange = 0;
- }
- }
-
- public void setTag(String tag) {
- mTag = tag;
- }
-
- private Sensor findBrightnessSensor(Context context, SensorManager sensorManager) {
- String sensorType = context.getString(R.string.doze_brightness_sensor_type);
- List<Sensor> sensorList = sensorManager.getSensorList(Sensor.TYPE_ALL);
- Sensor sensor = null;
- for (Sensor s : sensorList) {
- if (sensorType.equals(s.getStringType())) {
- sensor = s;
- break;
- }
- }
-
- return sensor;
- }
-
- /**
- * Returns {@code false} if a Proximity sensor is not available.
- */
- public boolean getSensorAvailable() {
- return mSensor != null;
- }
-
- /**
- * Add a listener.
- *
- * Registers itself with the {@link SensorManager} if this is the first listener
- * added.
- */
- public boolean register(ProximitySensorListener listener) {
- if (!getSensorAvailable()) {
- return false;
- }
-
- logDebug("using brightness sensor? " + mUsingBrightnessSensor);
- mListeners.add(listener);
- if (mListeners.size() == 1) {
- logDebug("registering sensor listener");
- mSensorManager.registerListener(
- mSensorEventListener, mSensor, SensorManager.SENSOR_DELAY_GAME);
- }
-
- return true;
- }
-
- /**
- * Remove a listener.
- *
- * If all listeners are removed from an instance of this class,
- * it will unregister itself with the SensorManager.
- */
- public void unregister(ProximitySensorListener listener) {
- mListeners.remove(listener);
- if (mListeners.size() == 0) {
- logDebug("unregistering sensor listener");
- mSensorManager.unregisterListener(mSensorEventListener);
- }
- }
-
- public boolean isNear() {
- return getSensorAvailable() && mNear;
- }
-
- private void onSensorEvent(SensorEvent event) {
- boolean near = event.values[0] < mMaxRange;
- if (mUsingBrightnessSensor) {
- near = event.values[0] == 0;
- }
- mNear = near;
- mListeners.forEach(proximitySensorListener ->
- proximitySensorListener.onProximitySensorEvent(
- new ProximityEvent(mNear, event.timestamp)));
- }
-
- /** Implement to be notified of ProximityEvents. */
- public interface ProximitySensorListener {
- /** Called when the ProximitySensor changes. */
- void onProximitySensorEvent(ProximityEvent proximityEvent);
- }
-
- /**
- * Returned when the near/far state of a {@link ProximitySensor} changes.
- */
- public static class ProximityEvent {
- private final boolean mNear;
- private final long mTimestampNs;
-
- public ProximityEvent(boolean near, long timestampNs) {
- mNear = near;
- mTimestampNs = timestampNs;
- }
-
- public boolean getNear() {
- return mNear;
- }
-
- public long getTimestampNs() {
- return mTimestampNs;
- }
- }
-
- private void logDebug(String msg) {
- if (DEBUG) {
- Log.d(TAG, (mTag != null ? "[" + mTag + "] " : "") + msg);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/util/AsyncSensorManager.java b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
similarity index 90%
rename from packages/SystemUI/src/com/android/systemui/util/AsyncSensorManager.java
rename to packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
index b9c5ee5..dcd0c58 100644
--- a/packages/SystemUI/src/com/android/systemui/util/AsyncSensorManager.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.util;
+package com.android.systemui.util.sensors;
import android.content.Context;
import android.hardware.HardwareBuffer;
@@ -56,23 +56,31 @@
private final SensorManager mInner;
private final List<Sensor> mSensorCache;
- private final HandlerThread mHandlerThread = new HandlerThread("async_sensor");
- @VisibleForTesting final Handler mHandler;
+ private final Handler mHandler;
private final List<SensorManagerPlugin> mPlugins;
@Inject
public AsyncSensorManager(Context context, PluginManager pluginManager) {
- this(context.getSystemService(SensorManager.class), pluginManager);
+ this(context.getSystemService(SensorManager.class), pluginManager, null);
}
@VisibleForTesting
- AsyncSensorManager(SensorManager sensorManager, PluginManager pluginManager) {
+ public AsyncSensorManager(
+ SensorManager sensorManager, PluginManager pluginManager, Handler handler) {
mInner = sensorManager;
- mHandlerThread.start();
- mHandler = new Handler(mHandlerThread.getLooper());
+ if (handler == null) {
+ HandlerThread handlerThread = new HandlerThread("async_sensor");
+ handlerThread.start();
+ mHandler = new Handler(handlerThread.getLooper());
+ } else {
+ mHandler = handler;
+ }
mSensorCache = mInner.getSensorList(Sensor.TYPE_ALL);
mPlugins = new ArrayList<>();
- pluginManager.addPluginListener(this, SensorManagerPlugin.class, true /* allowMultiple */);
+ if (pluginManager != null) {
+ pluginManager.addPluginListener(this, SensorManagerPlugin.class,
+ true /* allowMultiple */);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
new file mode 100644
index 0000000..c48bdde
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2019 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.util.sensors;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.os.Handler;
+import android.util.Log;
+
+import com.android.systemui.R;
+import com.android.systemui.shared.plugins.PluginManager;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.function.Consumer;
+
+import javax.inject.Inject;
+
+/**
+ * Simple wrapper around SensorManager customized for the Proximity sensor.
+ */
+public class ProximitySensor {
+ private static final String TAG = "ProxSensor";
+ private static final boolean DEBUG = false;
+
+ private final Sensor mSensor;
+ private final AsyncSensorManager mSensorManager;
+ private final boolean mUsingBrightnessSensor;
+ private final float mMaxRange;
+ private List<ProximitySensorListener> mListeners = new ArrayList<>();
+ private String mTag = null;
+ private ProximityEvent mLastEvent;
+ private int mSensorDelay = SensorManager.SENSOR_DELAY_NORMAL;
+ private boolean mPaused;
+ private boolean mRegistered;
+
+ private SensorEventListener mSensorEventListener = new SensorEventListener() {
+ @Override
+ public synchronized void onSensorChanged(SensorEvent event) {
+ onSensorEvent(event);
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ }
+ };
+
+ @Inject
+ public ProximitySensor(
+ Context context, AsyncSensorManager sensorManager, PluginManager pluginManager) {
+ mSensorManager = sensorManager;
+ Sensor sensor = findBrightnessSensor(context);
+
+ if (sensor == null) {
+ mUsingBrightnessSensor = false;
+ sensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
+ } else {
+ mUsingBrightnessSensor = true;
+ }
+ mSensor = sensor;
+ if (mSensor != null) {
+ mMaxRange = mSensor.getMaximumRange();
+ } else {
+ mMaxRange = 0;
+ }
+ }
+
+ public void setTag(String tag) {
+ mTag = tag;
+ }
+
+ public void setSensorDelay(int sensorDelay) {
+ mSensorDelay = sensorDelay;
+ }
+
+ /**
+ * Unregister with the {@link SensorManager} without unsetting listeners on this object.
+ */
+ public void pause() {
+ mPaused = true;
+ unregisterInternal();
+ }
+
+ /**
+ * Register with the {@link SensorManager}. No-op if no listeners are registered on this object.
+ */
+ public void resume() {
+ mPaused = false;
+ registerInternal();
+ }
+
+ private Sensor findBrightnessSensor(Context context) {
+ String sensorType = context.getString(R.string.doze_brightness_sensor_type);
+ List<Sensor> sensorList = mSensorManager.getSensorList(Sensor.TYPE_ALL);
+ Sensor sensor = null;
+ for (Sensor s : sensorList) {
+ if (sensorType.equals(s.getStringType())) {
+ sensor = s;
+ break;
+ }
+ }
+
+ return sensor;
+ }
+
+ /**
+ * Returns true if we are registered with the SensorManager.
+ */
+ public boolean isRegistered() {
+ return mRegistered;
+ }
+
+ /**
+ * Returns {@code false} if a Proximity sensor is not available.
+ */
+ public boolean getSensorAvailable() {
+ return mSensor != null;
+ }
+
+ /**
+ * Add a listener.
+ *
+ * Registers itself with the {@link SensorManager} if this is the first listener
+ * added. If a cool down is currently running, the sensor will be registered when it is over.
+ */
+ public boolean register(ProximitySensorListener listener) {
+ if (!getSensorAvailable()) {
+ return false;
+ }
+
+ logDebug("Using brightness sensor? " + mUsingBrightnessSensor);
+ mListeners.add(listener);
+ registerInternal();
+
+ return true;
+ }
+
+ private void registerInternal() {
+ if (mRegistered || mPaused || mListeners.isEmpty()) {
+ return;
+ }
+ logDebug("Registering sensor listener");
+ mRegistered = true;
+ mSensorManager.registerListener(mSensorEventListener, mSensor, mSensorDelay);
+ }
+
+ /**
+ * Remove a listener.
+ *
+ * If all listeners are removed from an instance of this class,
+ * it will unregister itself with the SensorManager.
+ */
+ public void unregister(ProximitySensorListener listener) {
+ mListeners.remove(listener);
+ if (mListeners.size() == 0) {
+ unregisterInternal();
+ }
+ }
+
+ private void unregisterInternal() {
+ if (!mRegistered) {
+ return;
+ }
+ logDebug("unregistering sensor listener");
+ mSensorManager.unregisterListener(mSensorEventListener);
+ mRegistered = false;
+ }
+
+ public Boolean isNear() {
+ return getSensorAvailable() && mLastEvent != null ? mLastEvent.getNear() : null;
+ }
+
+ /** Update all listeners with the last value this class received from the sensor. */
+ public void alertListeners() {
+ mListeners.forEach(proximitySensorListener ->
+ proximitySensorListener.onSensorEvent(mLastEvent));
+ }
+
+ private void onSensorEvent(SensorEvent event) {
+ boolean near = event.values[0] < mMaxRange;
+ if (mUsingBrightnessSensor) {
+ near = event.values[0] == 0;
+ }
+ mLastEvent = new ProximityEvent(near, event.timestamp);
+ alertListeners();
+ }
+
+ @Override
+ public String toString() {
+ return String.format("{registered=%s, paused=%s, near=%s, sensor=%s}",
+ isRegistered(), mPaused, isNear(), mSensor);
+ }
+
+ /**
+ * Convenience class allowing for briefly checking the proximity sensor.
+ */
+ public static class ProximityCheck implements Runnable {
+
+ private final ProximitySensor mSensor;
+ private final Handler mHandler;
+ private List<Consumer<Boolean>> mCallbacks = new ArrayList<>();
+
+ @Inject
+ public ProximityCheck(ProximitySensor sensor, Handler handler) {
+ mSensor = sensor;
+ mSensor.setTag("prox_check");
+ mHandler = handler;
+ mSensor.pause();
+ ProximitySensorListener listener = proximityEvent -> {
+ mCallbacks.forEach(
+ booleanConsumer ->
+ booleanConsumer.accept(
+ proximityEvent == null ? null : proximityEvent.getNear()));
+ mCallbacks.clear();
+ mSensor.pause();
+ };
+ mSensor.register(listener);
+ }
+
+ /** Set a descriptive tag for the sensors registration. */
+ public void setTag(String tag) {
+ mSensor.setTag(tag);
+ }
+
+ @Override
+ public void run() {
+ mSensor.pause();
+ mSensor.alertListeners();
+ }
+
+ /**
+ * Query the proximity sensor, timing out if no result.
+ */
+ public void check(long timeoutMs, Consumer<Boolean> callback) {
+ if (!mSensor.getSensorAvailable()) {
+ callback.accept(null);
+ }
+ mCallbacks.add(callback);
+ if (!mSensor.isRegistered()) {
+ mSensor.resume();
+ mHandler.postDelayed(this, timeoutMs);
+ }
+ }
+ }
+
+ /** Implement to be notified of ProximityEvents. */
+ public interface ProximitySensorListener {
+ /** Called when the ProximitySensor changes. */
+ void onSensorEvent(ProximityEvent proximityEvent);
+ }
+
+ /**
+ * Returned when the near/far state of a {@link ProximitySensor} changes.
+ */
+ public static class ProximityEvent {
+ private final boolean mNear;
+ private final long mTimestampNs;
+
+ public ProximityEvent(boolean near, long timestampNs) {
+ mNear = near;
+ mTimestampNs = timestampNs;
+ }
+
+ public boolean getNear() {
+ return mNear;
+ }
+
+ public long getTimestampNs() {
+ return mTimestampNs;
+ }
+
+ public long getTimestampMs() {
+ return mTimestampNs / 1000000;
+ }
+
+ @Override
+ public String toString() {
+ return String.format((Locale) null, "{near=%s, timestamp_ns=%d}", mNear, mTimestampNs);
+ }
+
+ }
+
+ private void logDebug(String msg) {
+ if (DEBUG) {
+ Log.d(TAG, (mTag != null ? "[" + mTag + "] " : "") + msg);
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java
index b979356..3561e34 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java
@@ -34,7 +34,7 @@
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.DeviceConfigProxyFake;
-import com.android.systemui.util.ProximitySensor;
+import com.android.systemui.util.sensors.ProximitySensor;
import org.junit.After;
import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ProximityClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ProximityClassifierTest.java
index 3fc5d72..35d59c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ProximityClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ProximityClassifierTest.java
@@ -30,7 +30,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.util.DeviceConfigProxyFake;
-import com.android.systemui.util.ProximitySensor;
+import com.android.systemui.util.sensors.ProximitySensor;
import org.junit.After;
import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
index 0c124ff..752e145 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
@@ -24,7 +24,7 @@
import static org.mockito.Mockito.withSettings;
import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.utils.hardware.FakeSensorManager;
+import com.android.systemui.util.sensors.FakeSensorManager;
import org.mockito.Answers;
import org.mockito.MockSettings;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
index 392c677..aa62e9a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
@@ -40,7 +40,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.utils.hardware.FakeSensorManager;
+import com.android.systemui.util.sensors.FakeSensorManager;
import org.junit.Before;
import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index cd6d1e0..ddd1685 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -44,7 +44,7 @@
import com.android.systemui.doze.DozeSensors.TriggerSensor;
import com.android.systemui.plugins.SensorManagerPlugin;
import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.util.AsyncSensorManager;
+import com.android.systemui.util.sensors.AsyncSensorManager;
import com.android.systemui.util.wakelock.WakeLock;
import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index e190f99..f7cd696 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -32,6 +32,7 @@
import android.os.Handler;
import android.os.Looper;
import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import androidx.test.filters.SmallTest;
@@ -39,9 +40,10 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dock.DockManager;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.util.sensors.AsyncSensorManager;
+import com.android.systemui.util.sensors.FakeSensorManager;
import com.android.systemui.util.wakelock.WakeLock;
import com.android.systemui.util.wakelock.WakeLockFake;
-import com.android.systemui.utils.hardware.FakeSensorManager;
import org.junit.Before;
import org.junit.BeforeClass;
@@ -55,12 +57,8 @@
private DozeTriggers mTriggers;
private DozeMachine mMachine;
private DozeHostFake mHost;
- private AmbientDisplayConfiguration mConfig;
- private DozeParameters mParameters;
private FakeSensorManager mSensors;
private Sensor mTapSensor;
- private WakeLock mWakeLock;
- private AlarmManager mAlarmManager;
private DockManager mDockManagerFake;
@BeforeClass
@@ -72,18 +70,21 @@
@Before
public void setUp() throws Exception {
mMachine = mock(DozeMachine.class);
- mAlarmManager = mock(AlarmManager.class);
+ AlarmManager alarmManager = mock(AlarmManager.class);
mHost = spy(new DozeHostFake());
- mConfig = DozeConfigurationUtil.createMockConfig();
- mParameters = DozeConfigurationUtil.createMockParameters();
+ AmbientDisplayConfiguration config = DozeConfigurationUtil.createMockConfig();
+ DozeParameters parameters = DozeConfigurationUtil.createMockParameters();
mSensors = spy(new FakeSensorManager(mContext));
mTapSensor = mSensors.getFakeTapSensor().getSensor();
- mWakeLock = new WakeLockFake();
+ WakeLock wakeLock = new WakeLockFake();
mDockManagerFake = mock(DockManager.class);
+ AsyncSensorManager asyncSensorManager =
+ new AsyncSensorManager(mSensors, null, new Handler());
- mTriggers = new DozeTriggers(mContext, mMachine, mHost, mAlarmManager, mConfig, mParameters,
- mSensors, Handler.createAsync(Looper.myLooper()), mWakeLock, true,
+ mTriggers = new DozeTriggers(mContext, mMachine, mHost, alarmManager, config, parameters,
+ asyncSensorManager, Handler.createAsync(Looper.myLooper()), wakeLock, true,
mDockManagerFake);
+ waitForSensorManager();
}
@Test
@@ -95,13 +96,14 @@
clearInvocations(mMachine);
mHost.callback.onNotificationAlerted(null /* pulseSuppressedListener */);
- mSensors.getMockProximitySensor().sendProximityResult(false); /* Near */
+ mSensors.getFakeProximitySensor().sendProximityResult(false); /* Near */
verify(mMachine, never()).requestState(any());
verify(mMachine, never()).requestPulse(anyInt());
mHost.callback.onNotificationAlerted(null /* pulseSuppressedListener */);
- mSensors.getMockProximitySensor().sendProximityResult(true); /* Far */
+ waitForSensorManager();
+ mSensors.getFakeProximitySensor().sendProximityResult(true); /* Far */
verify(mMachine).requestPulse(anyInt());
}
@@ -111,6 +113,7 @@
when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
mTriggers.transitionTo(DozeMachine.State.INITIALIZED, DozeMachine.State.DOZE);
+ waitForSensorManager();
verify(mSensors).requestTriggerSensor(any(), eq(mTapSensor));
clearInvocations(mSensors);
@@ -118,10 +121,12 @@
DozeMachine.State.DOZE_REQUEST_PULSE);
mTriggers.transitionTo(DozeMachine.State.DOZE_REQUEST_PULSE,
DozeMachine.State.DOZE_PULSING);
+ waitForSensorManager();
verify(mSensors).cancelTriggerSensor(any(), eq(mTapSensor));
clearInvocations(mSensors);
mTriggers.transitionTo(DozeMachine.State.DOZE_PULSING, DozeMachine.State.DOZE_PULSE_DONE);
+ waitForSensorManager();
verify(mSensors).requestTriggerSensor(any(), eq(mTapSensor));
}
@@ -133,4 +138,8 @@
mTriggers.transitionTo(DozeMachine.State.DOZE, DozeMachine.State.FINISH);
verify(mDockManagerFake).removeListener(any());
}
+
+ private void waitForSensorManager() {
+ TestableLooper.get(this).processAllMessages();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 57dd8c9..a027643 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -19,11 +19,12 @@
import static android.content.Intent.ACTION_USER_SWITCHED;
import static android.provider.Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
+
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -166,7 +167,10 @@
Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 1);
when(mNotificationData.isHighPriority(any())).thenReturn(false);
- assertTrue(mLockscreenUserManager.shouldShowOnKeyguard(mock(NotificationEntry.class)));
+ NotificationEntry entry = new NotificationEntryBuilder().build();
+ entry.setBucket(BUCKET_SILENT);
+
+ assertTrue(mLockscreenUserManager.shouldShowOnKeyguard(entry));
}
@Test
@@ -179,7 +183,9 @@
Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 0);
when(mNotificationData.isHighPriority(any())).thenReturn(false);
- assertFalse(mLockscreenUserManager.shouldShowOnKeyguard(mock(NotificationEntry.class)));
+ NotificationEntry entry = new NotificationEntryBuilder().build();
+ entry.setBucket(BUCKET_SILENT);
+ assertFalse(mLockscreenUserManager.shouldShowOnKeyguard(entry));
}
private class TestNotificationLockscreenUserManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
index 05f179e..820f465 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
@@ -51,6 +51,7 @@
private ArrayList<Notification.Action> mSmartActions = new ArrayList<>();
private ArrayList<CharSequence> mSmartReplies = new ArrayList<>();
private boolean mCanBubble = false;
+ private boolean mIsVisuallyInterruptive = false;
public RankingBuilder() {
}
@@ -98,7 +99,8 @@
mNoisy,
mSmartActions,
mSmartReplies,
- mCanBubble);
+ mCanBubble,
+ mIsVisuallyInterruptive);
return ranking;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index 53d6bff..30e02e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -183,7 +183,7 @@
0,
NotificationManager.IMPORTANCE_DEFAULT,
null, null,
- null, null, null, true, sentiment, false, -1, false, null, null, false);
+ null, null, null, true, sentiment, false, -1, false, null, null, false, false);
return true;
}).when(mRankingMap).getRanking(eq(key), any(NotificationListenerService.Ranking.class));
}
@@ -202,7 +202,7 @@
null, null,
null, null, null, true,
NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL, false, -1,
- false, smartActions, null, false);
+ false, smartActions, null, false, false);
return true;
}).when(mRankingMap).getRanking(eq(key), any(NotificationListenerService.Ranking.class));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
index a145c12..8d496a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
@@ -73,7 +73,7 @@
private DeviceProvisionedListener mProvisionedListener;
// TODO: Remove this once EntryManager no longer needs to be mocked
- private NotificationData mNotificationData = new NotificationData();
+ private NotificationData mNotificationData = new NotificationData(mContext);
private int mNextNotifId = 0;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
index 9bcbfbc..657ec61d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
@@ -32,6 +32,8 @@
import static com.android.systemui.statusbar.notification.collection.NotificationDataTest.TestableNotificationData.OVERRIDE_IMPORTANCE;
import static com.android.systemui.statusbar.notification.collection.NotificationDataTest.TestableNotificationData.OVERRIDE_RANK;
import static com.android.systemui.statusbar.notification.collection.NotificationDataTest.TestableNotificationData.OVERRIDE_VIS_EFFECTS;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
import static junit.framework.Assert.assertEquals;
@@ -48,6 +50,7 @@
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Person;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
@@ -136,7 +139,7 @@
mDependency.injectTestDependency(KeyguardEnvironment.class, mEnvironment);
when(mEnvironment.isDeviceProvisioned()).thenReturn(true);
when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
- mNotificationData = new TestableNotificationData();
+ mNotificationData = new TestableNotificationData(mContext);
mNotificationData.updateRanking(mock(NotificationListenerService.RankingMap.class));
mRow = new NotificationTestHelper(getContext()).createRow();
Dependency.get(InitController.class).executePostInitTasks();
@@ -527,8 +530,7 @@
@Test
public void testSort_samePriorityUsesNMSRank() {
- // NMS rank says A and then B. But A is not high priority and B is, so B should sort in
- // front
+ // NMS rank says A and then B, and they are the same priority so use that rank
Notification aN = new Notification.Builder(mContext, "test")
.setStyle(new Notification.MessagingStyle(""))
.build();
@@ -571,8 +573,7 @@
}
@Test
- public void testSort_properlySetsIsTopBucket() {
-
+ public void testSort_properlySetsAlertingBucket() {
Notification notification = new Notification.Builder(mContext, "test")
.build();
NotificationEntry entry = new NotificationEntryBuilder()
@@ -591,11 +592,11 @@
entry.setRow(mRow);
mNotificationData.add(entry);
- assertTrue(entry.isTopBucket());
+ assertEquals(entry.getBucket(), BUCKET_ALERTING);
}
@Test
- public void testSort_properlySetsIsNotTopBucket() {
+ public void testSort_properlySetsSilentBucket() {
Notification notification = new Notification.Builder(mContext, "test")
.build();
@@ -613,10 +614,9 @@
mNotificationData.rankingOverrides.put(entry.key(), override);
entry.setRow(mRow);
-
mNotificationData.add(entry);
- assertFalse(entry.isTopBucket());
+ assertEquals(entry.getBucket(), BUCKET_SILENT);
}
private void initStatusBarNotification(boolean allowDuringSetup) {
@@ -631,8 +631,8 @@
}
public static class TestableNotificationData extends NotificationData {
- public TestableNotificationData() {
- super();
+ public TestableNotificationData(Context context) {
+ super(context);
}
public static final String OVERRIDE_RANK = "r";
@@ -653,6 +653,7 @@
public static final String OVERRIDE_SMART_ACTIONS = "sa";
public static final String OVERRIDE_SMART_REPLIES = "sr";
public static final String OVERRIDE_BUBBLE = "cb";
+ public static final String OVERRIDE_VISUALLY_INTERRUPTIVE = "vi";
public Map<String, Bundle> rankingOverrides = new HashMap<>();
@@ -713,7 +714,9 @@
overrides.containsKey(OVERRIDE_SMART_REPLIES)
? overrides.getCharSequenceArrayList(OVERRIDE_SMART_REPLIES)
: currentReplies,
- overrides.getBoolean(OVERRIDE_BUBBLE, outRanking.canBubble()));
+ overrides.getBoolean(OVERRIDE_BUBBLE, outRanking.canBubble()),
+ overrides.getBoolean(OVERRIDE_VISUALLY_INTERRUPTIVE,
+ outRanking.visuallyInterruptive()));
} else {
outRanking.populate(
new RankingBuilder()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
index 524ad85..addceb5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
@@ -64,7 +64,7 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mRoundnessManager = new NotificationRoundnessManager(mBypassController);
+ mRoundnessManager = new NotificationRoundnessManager(mBypassController, mContext);
com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
NotificationTestHelper testHelper = new NotificationTestHelper(getContext());
mFirst = testHelper.createRow();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
index 59d0f91..56ed0e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
@@ -18,6 +18,9 @@
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -74,7 +77,7 @@
mActivityStarterDelegate,
mStatusBarStateController,
mConfigurationController,
- true);
+ 2);
// Required in order for the header inflation to work properly
when(mNssl.generateLayoutParams(any(AttributeSet.class)))
.thenReturn(new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
@@ -263,8 +266,8 @@
when(notifRow.getVisibility()).thenReturn(View.VISIBLE);
when(notifRow.getEntry().isHighPriority())
.thenReturn(children[i] == ChildType.HIPRI);
- when(notifRow.getEntry().isTopBucket())
- .thenReturn(children[i] == ChildType.HIPRI);
+ when(notifRow.getEntry().getBucket()).thenReturn(
+ children[i] == ChildType.HIPRI ? BUCKET_ALERTING : BUCKET_SILENT);
when(notifRow.getParent()).thenReturn(mNssl);
child = notifRow;
break;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index 2f24494..219aef1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -131,7 +131,7 @@
mKeyguardBypassController);
PulseExpansionHandler expansionHandler = new PulseExpansionHandler(mContext, coordinator,
mKeyguardBypassController, mHeadsUpManager,
- mock(NotificationRoundnessManager.class));
+ mock(NotificationRoundnessManager.class), mStatusBarStateController);
mNotificationPanelView = new TestableNotificationPanelView(coordinator, expansionHandler,
mKeyguardBypassController);
mNotificationPanelView.setHeadsUpManager(mHeadsUpManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 5d3cdc8..2623b46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -243,7 +243,7 @@
}
@Test
- public void transitionToPulsing_withFrontAlphaUpdates() {
+ public void transitionToPulsing() {
// Pre-condition
// Need to go to AoD first because PULSING doesn't change
// the back scrim opacity - otherwise it would hide AoD wallpapers.
@@ -267,22 +267,11 @@
true /* behind */,
false /* bubble */);
- // ... and when ambient goes dark, front scrim should be semi-transparent
- mScrimController.setAodFrontScrimAlpha(0.5f);
- mScrimController.finishAnimationsImmediately();
- // Front scrim should be semi-transparent
- assertScrimAlpha(SEMI_TRANSPARENT /* front */,
- OPAQUE /* back */,
- TRANSPARENT /* bubble */);
-
mScrimController.setWakeLockScreenSensorActive(true);
mScrimController.finishAnimationsImmediately();
- assertScrimAlpha(SEMI_TRANSPARENT /* front */,
+ assertScrimAlpha(TRANSPARENT /* front */,
SEMI_TRANSPARENT /* back */,
TRANSPARENT /* bubble */);
-
- // Reset value since enums are static.
- mScrimController.setAodFrontScrimAlpha(0f);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/AsyncSensorManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java
similarity index 71%
rename from packages/SystemUI/tests/src/com/android/systemui/util/AsyncSensorManagerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java
index 4a9b1b3..9149599 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/AsyncSensorManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-package com.android.systemui.util;
+package com.android.systemui.util.sensors;
-import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@@ -24,15 +23,15 @@
import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
+import android.os.Handler;
import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.SensorManagerPlugin;
import com.android.systemui.shared.plugins.PluginManager;
-import com.android.systemui.utils.hardware.FakeSensorManager;
import org.junit.Before;
import org.junit.Test;
@@ -40,20 +39,21 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
public class AsyncSensorManagerTest extends SysuiTestCase {
- private TestableAsyncSensorManager mAsyncSensorManager;
- private FakeSensorManager mFakeSensorManager;
+ private AsyncSensorManager mAsyncSensorManager;
private SensorEventListener mListener;
- private FakeSensorManager.MockProximitySensor mSensor;
+ private FakeSensorManager.FakeProximitySensor mSensor;
private PluginManager mPluginManager;
@Before
public void setUp() throws Exception {
mPluginManager = mock(PluginManager.class);
- mFakeSensorManager = new FakeSensorManager(mContext);
- mAsyncSensorManager = new TestableAsyncSensorManager(mFakeSensorManager);
- mSensor = mFakeSensorManager.getMockProximitySensor();
+ FakeSensorManager fakeSensorManager = new FakeSensorManager(mContext);
+ mAsyncSensorManager = new AsyncSensorManager(
+ fakeSensorManager, mPluginManager, new Handler());
+ mSensor = fakeSensorManager.getFakeProximitySensor();
mListener = mock(SensorEventListener.class);
}
@@ -61,7 +61,7 @@
public void registerListenerImpl() throws Exception {
mAsyncSensorManager.registerListener(mListener, mSensor.getSensor(), 100);
- mAsyncSensorManager.waitUntilRequestsCompleted();
+ waitUntilRequestsCompleted();
// Verify listener was registered.
mSensor.sendProximityResult(true);
@@ -73,7 +73,7 @@
mAsyncSensorManager.registerListener(mListener, mSensor.getSensor(), 100);
mAsyncSensorManager.unregisterListener(mListener);
- mAsyncSensorManager.waitUntilRequestsCompleted();
+ waitUntilRequestsCompleted();
// Verify listener was unregistered.
mSensor.sendProximityResult(true);
@@ -85,7 +85,7 @@
mAsyncSensorManager.registerListener(mListener, mSensor.getSensor(), 100);
mAsyncSensorManager.unregisterListener(mListener, mSensor.getSensor());
- mAsyncSensorManager.waitUntilRequestsCompleted();
+ waitUntilRequestsCompleted();
// Verify listener was unregistered.
mSensor.sendProximityResult(true);
@@ -98,13 +98,7 @@
eq(SensorManagerPlugin.class), eq(true) /* allowMultiple */);
}
- private class TestableAsyncSensorManager extends AsyncSensorManager {
- public TestableAsyncSensorManager(SensorManager sensorManager) {
- super(sensorManager, mPluginManager);
- }
-
- public void waitUntilRequestsCompleted() {
- assertTrue(mHandler.runWithScissors(() -> {}, 0));
- }
+ public void waitUntilRequestsCompleted() {
+ TestableLooper.get(this).processAllMessages();
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/hardware/FakeSensorManager.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/hardware/FakeSensorManager.java
rename to packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java
index 29b8ab60..1deb495 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/hardware/FakeSensorManager.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java
@@ -11,10 +11,10 @@
* 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
+ * limitations under the License.
*/
-package com.android.systemui.utils.hardware;
+package com.android.systemui.util.sensors;
import android.content.Context;
import android.hardware.HardwareBuffer;
@@ -54,7 +54,7 @@
public static final String TAP_SENSOR_TYPE = "tapSensorType";
- private final MockProximitySensor mMockProximitySensor;
+ private final FakeProximitySensor mFakeProximitySensor;
private final FakeGenericSensor mFakeLightSensor;
private final FakeGenericSensor mFakeTapSensor;
private final FakeGenericSensor[] mSensors;
@@ -68,14 +68,14 @@
}
mSensors = new FakeGenericSensor[]{
- mMockProximitySensor = new MockProximitySensor(proxSensor),
+ mFakeProximitySensor = new FakeProximitySensor(proxSensor),
mFakeLightSensor = new FakeGenericSensor(createSensor(Sensor.TYPE_LIGHT, null)),
mFakeTapSensor = new FakeGenericSensor(createSensor(99, TAP_SENSOR_TYPE))
};
}
- public MockProximitySensor getMockProximitySensor() {
- return mMockProximitySensor;
+ public FakeProximitySensor getFakeProximitySensor() {
+ return mFakeProximitySensor;
}
public FakeGenericSensor getFakeLightSensor() {
@@ -231,9 +231,9 @@
setter.invoke(sensor, type);
}
- public class MockProximitySensor extends FakeGenericSensor {
+ public class FakeProximitySensor extends FakeGenericSensor {
- private MockProximitySensor(Sensor sensor) {
+ private FakeProximitySensor(Sensor sensor) {
super(sensor);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorTest.java
new file mode 100644
index 0000000..6d13408
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorTest.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2019 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.util.sensors;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Handler;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class ProximitySensorTest extends SysuiTestCase {
+
+ private ProximitySensor mProximitySensor;
+ private FakeSensorManager.FakeProximitySensor mFakeProximitySensor;
+
+ @Before
+ public void setUp() throws Exception {
+ FakeSensorManager sensorManager = new FakeSensorManager(getContext());
+ AsyncSensorManager asyncSensorManager = new AsyncSensorManager(
+ sensorManager, null, new Handler());
+ mFakeProximitySensor = sensorManager.getFakeProximitySensor();
+ mProximitySensor = new ProximitySensor(getContext(), asyncSensorManager, null);
+ }
+
+ @Test
+ public void testSingleListener() {
+ TestableListener listener = new TestableListener();
+
+ assertFalse(mProximitySensor.isRegistered());
+ mProximitySensor.register(listener);
+ waitForSensorManager();
+ assertTrue(mProximitySensor.isRegistered());
+ assertNull(listener.mLastEvent);
+
+ mFakeProximitySensor.sendProximityResult(true);
+ assertFalse(listener.mLastEvent.getNear());
+ assertEquals(listener.mCallCount, 1);
+ mFakeProximitySensor.sendProximityResult(false);
+ assertTrue(listener.mLastEvent.getNear());
+ assertEquals(listener.mCallCount, 2);
+
+ mProximitySensor.unregister(listener);
+ waitForSensorManager();
+ }
+
+ @Test
+ public void testMultiListener() {
+ TestableListener listenerA = new TestableListener();
+ TestableListener listenerB = new TestableListener();
+
+ assertFalse(mProximitySensor.isRegistered());
+
+ mProximitySensor.register(listenerA);
+ waitForSensorManager();
+ assertTrue(mProximitySensor.isRegistered());
+ mProximitySensor.register(listenerB);
+ waitForSensorManager();
+ assertTrue(mProximitySensor.isRegistered());
+ assertNull(listenerA.mLastEvent);
+ assertNull(listenerB.mLastEvent);
+
+ mFakeProximitySensor.sendProximityResult(true);
+ assertFalse(listenerA.mLastEvent.getNear());
+ assertFalse(listenerB.mLastEvent.getNear());
+ assertEquals(listenerA.mCallCount, 1);
+ assertEquals(listenerB.mCallCount, 1);
+ mFakeProximitySensor.sendProximityResult(false);
+ assertTrue(listenerA.mLastEvent.getNear());
+ assertTrue(listenerB.mLastEvent.getNear());
+ assertEquals(listenerA.mCallCount, 2);
+ assertEquals(listenerB.mCallCount, 2);
+
+ mProximitySensor.unregister(listenerA);
+ mProximitySensor.unregister(listenerB);
+ waitForSensorManager();
+ }
+
+ @Test
+ public void testUnregister() {
+ TestableListener listener = new TestableListener();
+
+ assertFalse(mProximitySensor.isRegistered());
+ mProximitySensor.register(listener);
+ waitForSensorManager();
+ assertTrue(mProximitySensor.isRegistered());
+ assertNull(listener.mLastEvent);
+
+ mFakeProximitySensor.sendProximityResult(true);
+ assertFalse(listener.mLastEvent.getNear());
+ assertEquals(listener.mCallCount, 1);
+
+ mProximitySensor.unregister(listener);
+ waitForSensorManager();
+ assertFalse(mProximitySensor.isRegistered());
+ }
+
+ @Test
+ public void testPauseAndResume() {
+ TestableListener listener = new TestableListener();
+
+ assertFalse(mProximitySensor.isRegistered());
+ mProximitySensor.register(listener);
+ waitForSensorManager();
+ assertTrue(mProximitySensor.isRegistered());
+ assertNull(listener.mLastEvent);
+
+ mFakeProximitySensor.sendProximityResult(true);
+ assertFalse(listener.mLastEvent.getNear());
+ assertEquals(listener.mCallCount, 1);
+
+ mProximitySensor.pause();
+ waitForSensorManager();
+ assertFalse(mProximitySensor.isRegistered());
+
+ // More events do nothing when paused.
+ mFakeProximitySensor.sendProximityResult(true);
+ assertFalse(listener.mLastEvent.getNear());
+ assertEquals(listener.mCallCount, 1);
+ mFakeProximitySensor.sendProximityResult(false);
+ assertFalse(listener.mLastEvent.getNear());
+ assertEquals(listener.mCallCount, 1);
+
+ mProximitySensor.resume();
+ waitForSensorManager();
+ assertTrue(mProximitySensor.isRegistered());
+ // Still matches our previous call
+ assertFalse(listener.mLastEvent.getNear());
+ assertEquals(listener.mCallCount, 1);
+
+ mFakeProximitySensor.sendProximityResult(true);
+ assertFalse(listener.mLastEvent.getNear());
+ assertEquals(listener.mCallCount, 2);
+
+ mProximitySensor.unregister(listener);
+ waitForSensorManager();
+ assertFalse(mProximitySensor.isRegistered());
+ }
+
+ @Test
+ public void testAlertListeners() {
+ TestableListener listenerA = new TestableListener();
+ TestableListener listenerB = new TestableListener();
+
+ assertFalse(mProximitySensor.isRegistered());
+
+ mProximitySensor.register(listenerA);
+ mProximitySensor.register(listenerB);
+ waitForSensorManager();
+ assertTrue(mProximitySensor.isRegistered());
+ assertNull(listenerA.mLastEvent);
+ assertNull(listenerB.mLastEvent);
+
+ mProximitySensor.alertListeners();
+ assertNull(listenerA.mLastEvent);
+ assertEquals(listenerA.mCallCount, 1);
+ assertNull(listenerB.mLastEvent);
+ assertEquals(listenerB.mCallCount, 1);
+
+ mFakeProximitySensor.sendProximityResult(false);
+ assertTrue(listenerA.mLastEvent.getNear());
+ assertEquals(listenerA.mCallCount, 2);
+ assertTrue(listenerB.mLastEvent.getNear());
+ assertEquals(listenerB.mCallCount, 2);
+
+ mProximitySensor.unregister(listenerA);
+ mProximitySensor.unregister(listenerB);
+ waitForSensorManager();
+ }
+
+ class TestableListener implements ProximitySensor.ProximitySensorListener {
+ ProximitySensor.ProximityEvent mLastEvent;
+ int mCallCount = 0;
+
+ @Override
+ public void onSensorEvent(ProximitySensor.ProximityEvent proximityEvent) {
+ mLastEvent = proximityEvent;
+ mCallCount++;
+ }
+
+ void reset() {
+ mLastEvent = null;
+ mCallCount = 0;
+ }
+ };
+
+ private void waitForSensorManager() {
+ TestableLooper.get(this).processAllMessages();
+ }
+
+}
diff --git a/proto/src/wifi.proto b/proto/src/wifi.proto
index 8ad2489..353a187 100644
--- a/proto/src/wifi.proto
+++ b/proto/src/wifi.proto
@@ -1792,6 +1792,25 @@
// Indicates if we are logging LinkSpeedCount in metrics
optional bool link_speed_counts_logging_enabled = 4;
+
+ // Duration for evaluating Wifi condition to trigger a data stall
+ // measured in milliseconds
+ optional int32 data_stall_duration_ms = 5;
+
+ // Threshold of Tx throughput below which to trigger a data stall
+ // measured in Mbps
+ optional int32 data_stall_tx_tput_thr_mbps = 6;
+
+ // Threshold of Rx throughput below which to trigger a data stall
+ // measured in Mbps
+ optional int32 data_stall_rx_tput_thr_mbps = 7;
+
+ // Threshold of Tx packet error rate above which to trigger a data stall
+ // in percentage
+ optional int32 data_stall_tx_per_thr = 8;
+
+ // Threshold of CCA level above which to trigger a data stall in percentage
+ optional int32 data_stall_cca_level_thr = 9;
}
message WifiIsUnusableEvent {
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index cdb062d..4f021ad 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -23,7 +23,7 @@
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
-import android.accessibilityservice.AccessibilityGestureInfo;
+import android.accessibilityservice.AccessibilityGestureEvent;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.accessibilityservice.IAccessibilityServiceConnection;
@@ -1173,9 +1173,9 @@
}
}
- public void notifyGesture(AccessibilityGestureInfo gestureInfo) {
+ public void notifyGesture(AccessibilityGestureEvent gestureEvent) {
mInvocationHandler.obtainMessage(InvocationHandler.MSG_ON_GESTURE,
- gestureInfo).sendToTarget();
+ gestureEvent).sendToTarget();
}
public void notifyClearAccessibilityNodeInfoCache() {
@@ -1264,7 +1264,7 @@
}
}
- private void notifyGestureInternal(AccessibilityGestureInfo gestureInfo) {
+ private void notifyGestureInternal(AccessibilityGestureEvent gestureInfo) {
final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
if (listener != null) {
try {
@@ -1469,7 +1469,7 @@
final int type = message.what;
switch (type) {
case MSG_ON_GESTURE: {
- notifyGestureInternal((AccessibilityGestureInfo) message.obj);
+ notifyGestureInternal((AccessibilityGestureEvent) message.obj);
} break;
case MSG_CLEAR_ACCESSIBILITY_CACHE: {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index feb7329..ba4d89c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -27,7 +27,7 @@
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import android.Manifest;
-import android.accessibilityservice.AccessibilityGestureInfo;
+import android.accessibilityservice.AccessibilityGestureEvent;
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceClient;
@@ -828,12 +828,17 @@
}
}
-
- public boolean onGesture(AccessibilityGestureInfo gestureInfo) {
+ /**
+ * Called when a gesture is detected on a display.
+ *
+ * @param gestureEvent the detail of the gesture.
+ * @return true if the event is handled.
+ */
+ public boolean onGesture(AccessibilityGestureEvent gestureEvent) {
synchronized (mLock) {
- boolean handled = notifyGestureLocked(gestureInfo, false);
+ boolean handled = notifyGestureLocked(gestureEvent, false);
if (!handled) {
- handled = notifyGestureLocked(gestureInfo, true);
+ handled = notifyGestureLocked(gestureEvent, true);
}
return handled;
}
@@ -1028,7 +1033,7 @@
}
}
- private boolean notifyGestureLocked(AccessibilityGestureInfo gestureInfo, boolean isDefault) {
+ private boolean notifyGestureLocked(AccessibilityGestureEvent gestureEvent, boolean isDefault) {
// TODO: Now we are giving the gestures to the last enabled
// service that can handle them which is the last one
// in our list since we write the last enabled as the
@@ -1042,7 +1047,7 @@
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
AccessibilityServiceConnection service = state.mBoundServices.get(i);
if (service.mRequestTouchExplorationMode && service.mIsDefault == isDefault) {
- service.notifyGesture(gestureInfo);
+ service.notifyGesture(gestureEvent);
return true;
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/AccessibilityGestureDetector.java b/services/accessibility/java/com/android/server/accessibility/gestures/AccessibilityGestureDetector.java
index 9101a01..7e8fb29 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/AccessibilityGestureDetector.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/AccessibilityGestureDetector.java
@@ -16,7 +16,7 @@
package com.android.server.accessibility.gestures;
-import android.accessibilityservice.AccessibilityGestureInfo;
+import android.accessibilityservice.AccessibilityGestureEvent;
import android.accessibilityservice.AccessibilityService;
import android.content.Context;
import android.gesture.GesturePoint;
@@ -119,11 +119,11 @@
/**
* Called when an event stream is recognized as a gesture.
*
- * @param gestureInfo Information about the gesture.
+ * @param gestureEvent Information about the gesture.
*
* @return true if the event is consumed, else false
*/
- boolean onGestureCompleted(AccessibilityGestureInfo gestureInfo);
+ boolean onGestureCompleted(AccessibilityGestureEvent gestureEvent);
/**
* Called when the system has decided an event stream doesn't match any
@@ -567,19 +567,19 @@
switch (direction) {
case LEFT:
return mListener.onGestureCompleted(
- new AccessibilityGestureInfo(AccessibilityService.GESTURE_SWIPE_LEFT,
+ new AccessibilityGestureEvent(AccessibilityService.GESTURE_SWIPE_LEFT,
displayId));
case RIGHT:
return mListener.onGestureCompleted(
- new AccessibilityGestureInfo(AccessibilityService.GESTURE_SWIPE_RIGHT,
+ new AccessibilityGestureEvent(AccessibilityService.GESTURE_SWIPE_RIGHT,
displayId));
case UP:
return mListener.onGestureCompleted(
- new AccessibilityGestureInfo(AccessibilityService.GESTURE_SWIPE_UP,
+ new AccessibilityGestureEvent(AccessibilityService.GESTURE_SWIPE_UP,
displayId));
case DOWN:
return mListener.onGestureCompleted(
- new AccessibilityGestureInfo(AccessibilityService.GESTURE_SWIPE_DOWN,
+ new AccessibilityGestureEvent(AccessibilityService.GESTURE_SWIPE_DOWN,
displayId));
default:
// Do nothing.
@@ -600,7 +600,7 @@
int segmentDirection1 = toDirection(dX1, dY1);
int gestureId = DIRECTIONS_TO_GESTURE_ID[segmentDirection0][segmentDirection1];
return mListener.onGestureCompleted(
- new AccessibilityGestureInfo(gestureId, displayId));
+ new AccessibilityGestureEvent(gestureId, displayId));
}
// else if (path.size() < 2 || 3 < path.size()) then no gesture recognized.
return mListener.onGestureCancelled(event, policyFlags);
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index a338b90..7044c4d 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -20,7 +20,7 @@
import static com.android.server.accessibility.gestures.TouchState.ALL_POINTER_ID_BITS;
-import android.accessibilityservice.AccessibilityGestureInfo;
+import android.accessibilityservice.AccessibilityGestureEvent;
import android.content.Context;
import android.graphics.Point;
import android.os.Handler;
@@ -343,14 +343,14 @@
}
@Override
- public boolean onGestureCompleted(AccessibilityGestureInfo gestureInfo) {
+ public boolean onGestureCompleted(AccessibilityGestureEvent gestureEvent) {
if (!mState.isGestureDetecting()) {
return false;
}
endGestureDetection(true);
- mAms.onGesture(gestureInfo);
+ mAms.onGesture(gestureEvent);
return true;
}
diff --git a/services/core/java/com/android/server/MountServiceIdler.java b/services/core/java/com/android/server/MountServiceIdler.java
index 79b1226..6bc1a57 100644
--- a/services/core/java/com/android/server/MountServiceIdler.java
+++ b/services/core/java/com/android/server/MountServiceIdler.java
@@ -99,32 +99,28 @@
public static void scheduleIdlePass(Context context) {
JobScheduler tm = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
- final long today3AM = MidnightInTime(0, 0).getTimeInMillis();
- final long today4AM = MidnightInTime(0, 1).getTimeInMillis();
+ final long today3AM = offsetFromTodayMidnight(0, 3).getTimeInMillis();
+ final long today4AM = offsetFromTodayMidnight(0, 4).getTimeInMillis();
+ final long tomorrow3AM = offsetFromTodayMidnight(1, 3).getTimeInMillis();
- long nextScheduleTime, maxScheduleTime;
+ long nextScheduleTime;
if (System.currentTimeMillis() > today3AM && System.currentTimeMillis() < today4AM) {
nextScheduleTime = TimeUnit.SECONDS.toMillis(10);
- maxScheduleTime = today4AM - System.currentTimeMillis();
} else {
- final long tomorrow3AM = MidnightInTime(1, 0).getTimeInMillis();
- final long twodays3AM = MidnightInTime(2, 0).getTimeInMillis();
nextScheduleTime = tomorrow3AM - System.currentTimeMillis(); // 3AM tomorrow
- maxScheduleTime = twodays3AM - System.currentTimeMillis(); // 3AM in two days
}
JobInfo.Builder builder = new JobInfo.Builder(MOUNT_JOB_ID, sIdleService);
builder.setRequiresDeviceIdle(true);
- builder.setRequiresCharging(true);
+ builder.setRequiresBatteryNotLow(true);
builder.setMinimumLatency(nextScheduleTime);
- builder.setOverrideDeadline(maxScheduleTime);
tm.schedule(builder.build());
}
- private static Calendar MidnightInTime(int nDays, int nHours) {
+ private static Calendar offsetFromTodayMidnight(int nDays, int nHours) {
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
- calendar.set(Calendar.HOUR_OF_DAY, 3 + nHours);
+ calendar.set(Calendar.HOUR_OF_DAY, nHours);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 4403efc..bc50956 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -87,6 +87,8 @@
// Number of package failures within the duration above before we notify observers
@VisibleForTesting
static final int DEFAULT_TRIGGER_FAILURE_COUNT = 5;
+ @VisibleForTesting
+ static final long DEFAULT_OBSERVING_DURATION_MS = TimeUnit.DAYS.toMillis(2);
// Whether explicit health checks are enabled or not
private static final boolean DEFAULT_EXPLICIT_HEALTH_CHECK_ENABLED = true;
@@ -225,8 +227,10 @@
* check state will be reset to a default depending on if the package is contained in
* {@link mPackagesWithExplicitHealthCheckEnabled}.
*
- * @throws IllegalArgumentException if {@code packageNames} is empty
- * or {@code durationMs} is less than 1
+ * <p>If {@code packageNames} is empty, this will be a no-op.
+ *
+ * <p>If {@code durationMs} is less than 1, a default monitoring duration
+ * {@link #DEFAULT_OBSERVING_DURATION_MS} will be used.
*/
public void startObservingHealth(PackageHealthObserver observer, List<String> packageNames,
long durationMs) {
@@ -235,9 +239,9 @@
return;
}
if (durationMs < 1) {
- // TODO: Instead of failing, monitor for default? 48hrs?
- throw new IllegalArgumentException("Invalid duration " + durationMs + "ms for observer "
+ Slog.wtf(TAG, "Invalid duration " + durationMs + "ms for observer "
+ observer.getName() + ". Not observing packages " + packageNames);
+ durationMs = DEFAULT_OBSERVING_DURATION_MS;
}
List<MonitoredPackage> packages = new ArrayList<>();
@@ -808,7 +812,6 @@
*/
private static class ObserverInternal {
public final String name;
- //TODO(b/120598832): Add getter for mPackages
@GuardedBy("mLock")
public final ArrayMap<String, MonitoredPackage> packages = new ArrayMap<>();
@Nullable
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 1675b94..e7569be 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1748,8 +1748,8 @@
s.instanceName, s.processName);
// Once the apps have become associated, if one of them is caller is ephemeral
// the target app should now be able to see the calling app
- mAm.grantEphemeralAccessLocked(callerApp.userId, service,
- UserHandle.getAppId(s.appInfo.uid), UserHandle.getAppId(callerApp.uid));
+ mAm.grantImplicitAccess(callerApp.userId, service,
+ UserHandle.getAppId(callerApp.uid), UserHandle.getAppId(s.appInfo.uid));
AppBindRecord b = s.retrieveAppBindingLocked(service, callerApp);
ConnectionRecord c = new ConnectionRecord(b, activity,
@@ -2802,8 +2802,9 @@
mAm.mUgmInternal.grantUriPermissionUncheckedFromIntent(si.neededGrants,
si.getUriPermissionsLocked());
}
- mAm.grantEphemeralAccessLocked(r.userId, si.intent, UserHandle.getAppId(r.appInfo.uid),
- UserHandle.getAppId(si.callingId));
+ mAm.grantImplicitAccess(r.userId, si.intent, UserHandle.getAppId(si.callingId),
+ UserHandle.getAppId(r.appInfo.uid)
+ );
bumpServiceExecutingLocked(r, execInFg, "start");
if (!oomAdjusted) {
oomAdjusted = true;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 55d422d..217a6c6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -6115,10 +6115,9 @@
}
@VisibleForTesting
- public void grantEphemeralAccessLocked(int userId, Intent intent,
- int targetAppId, int ephemeralAppId) {
+ public void grantImplicitAccess(int userId, Intent intent, int callingAppId, int targetAppId) {
getPackageManagerInternalLocked().
- grantEphemeralAccess(userId, intent, targetAppId, ephemeralAppId);
+ grantImplicitAccess(userId, intent, callingAppId, targetAppId);
}
/**
@@ -7088,9 +7087,10 @@
}
checkTime(startTime, "getContentProviderImpl: done!");
- grantEphemeralAccessLocked(userId, null /*intent*/,
- UserHandle.getAppId(cpi.applicationInfo.uid),
- UserHandle.getAppId(Binder.getCallingUid()));
+ grantImplicitAccess(userId, null /*intent*/,
+ UserHandle.getAppId(Binder.getCallingUid()),
+ UserHandle.getAppId(cpi.applicationInfo.uid)
+ );
}
// Wait for the provider to be published...
@@ -19296,12 +19296,6 @@
// null permissions means all permissions are targeted
return (mPermissions == null || ArrayUtils.contains(mPermissions, permission));
}
-
- @Override
- public String toString() {
- return "ShellDelegate{targetPackageName=" + mTargetPackageName
- + ", permissions=" + mPermissions + "}";
- }
}
/**
diff --git a/services/core/java/com/android/server/am/MemoryStatUtil.java b/services/core/java/com/android/server/am/MemoryStatUtil.java
index e73abd1..fd64df9 100644
--- a/services/core/java/com/android/server/am/MemoryStatUtil.java
+++ b/services/core/java/com/android/server/am/MemoryStatUtil.java
@@ -63,8 +63,6 @@
private static final Pattern CACHE_IN_BYTES = Pattern.compile("total_cache (\\d+)");
private static final Pattern SWAP_IN_BYTES = Pattern.compile("total_swap (\\d+)");
- private static final Pattern RSS_HIGH_WATERMARK_IN_KILOBYTES =
- Pattern.compile("VmHWM:\\s*(\\d+)\\s*kB");
private static final Pattern PROCFS_RSS_IN_KILOBYTES =
Pattern.compile("VmRSS:\\s*(\\d+)\\s*kB");
private static final Pattern PROCFS_ANON_RSS_IN_KILOBYTES =
@@ -113,15 +111,6 @@
}
/**
- * Reads RSS high-water mark of a process from procfs. Returns value of the VmHWM field in
- * /proc/PID/status in kilobytes or 0 if not available.
- */
- public static int readRssHighWaterMarkFromProcfs(int pid) {
- final String statusPath = String.format(Locale.US, PROC_STATUS_FILE_FMT, pid);
- return parseVmHWMFromProcfs(readFileContents(statusPath));
- }
-
- /**
* Reads cmdline of a process from procfs.
*
* Returns content of /proc/pid/cmdline (e.g. /system/bin/statsd) or an empty string
@@ -204,19 +193,6 @@
}
/**
- * Parses RSS high-water mark out from the contents of the /proc/pid/status file in procfs. The
- * returned value is in kilobytes.
- */
- @VisibleForTesting
- static int parseVmHWMFromProcfs(String procStatusContents) {
- if (procStatusContents == null || procStatusContents.isEmpty()) {
- return 0;
- }
- return (int) tryParseLong(RSS_HIGH_WATERMARK_IN_KILOBYTES, procStatusContents);
- }
-
-
- /**
* Parses cmdline out of the contents of the /proc/pid/cmdline file in procfs.
*
* Parsing is required to strip anything after first null byte.
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 09f5286..96af74a 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -79,7 +79,7 @@
private static final int MSG_SWITCH_USER = 1;
private static final int RETRY_DELAY_TIME = 20; //ms
- private static final int RETRY_TIMES = 30;
+ private static final int RETRY_TIMES = 60;
// Maximum entries to keep in usage history before dumping out
private static final int MAX_USAGE_HISTORY = 100;
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index fc38735..81e507c 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -19,7 +19,9 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.util.Slog;
+import android.util.StatsLog;
+import com.android.internal.compat.ChangeReporter;
import com.android.internal.compat.IPlatformCompat;
import com.android.internal.util.DumpUtils;
@@ -34,23 +36,27 @@
private static final String TAG = "Compatibility";
private final Context mContext;
+ private final ChangeReporter mChangeReporter;
public PlatformCompat(Context context) {
mContext = context;
+ mChangeReporter = new ChangeReporter();
}
@Override
public void reportChange(long changeId, ApplicationInfo appInfo) {
- Slog.d(TAG, "Compat change reported: " + changeId + "; UID " + appInfo.uid);
- // TODO log via StatsLog
+ reportChange(changeId, appInfo, StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__LOGGED);
}
@Override
public boolean isChangeEnabled(long changeId, ApplicationInfo appInfo) {
if (CompatConfig.get().isChangeEnabled(changeId, appInfo)) {
- reportChange(changeId, appInfo);
+ reportChange(changeId, appInfo,
+ StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__ENABLED);
return true;
}
+ reportChange(changeId, appInfo,
+ StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__DISABLED);
return false;
}
@@ -59,4 +65,13 @@
if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, "platform_compat", pw)) return;
CompatConfig.get().dumpConfig(pw);
}
+
+ private void reportChange(long changeId, ApplicationInfo appInfo, int state) {
+ int uid = appInfo.uid;
+ //TODO(b/138374585): Implement rate limiting for the logs.
+ Slog.d(TAG, ChangeReporter.createLogString(uid, changeId, state));
+ mChangeReporter.reportChange(uid, changeId,
+ state, /* source */
+ StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__SYSTEM_SERVER);
+ }
}
diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java
index 9b9f4de..bc05154 100644
--- a/services/core/java/com/android/server/notification/NotificationComparator.java
+++ b/services/core/java/com/android/server/notification/NotificationComparator.java
@@ -129,6 +129,12 @@
return -1 * Integer.compare(leftPriority, rightPriority);
}
+ final boolean leftInterruptive = left.isInterruptive();
+ final boolean rightInterruptive = right.isInterruptive();
+ if (leftInterruptive != rightInterruptive) {
+ return -1 * Boolean.compare(leftInterruptive, rightInterruptive);
+ }
+
// then break ties by time, most recent first
return -1 * Long.compare(left.getRankingTimeMs(), right.getRankingTimeMs());
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 2d4c6cf..d480cb6e 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -5759,7 +5759,9 @@
notification.flags |=
old.getNotification().flags & FLAG_FOREGROUND_SERVICE;
r.isUpdate = true;
- r.setTextChanged(isVisuallyInterruptive(old, r));
+ final boolean isInterruptive = isVisuallyInterruptive(old, r);
+ r.setTextChanged(isInterruptive);
+ r.setInterruptive(isInterruptive);
}
mNotificationsByKey.put(n.getKey(), r);
@@ -5858,7 +5860,6 @@
Notification oldN = old.sbn.getNotification();
Notification newN = r.sbn.getNotification();
-
if (oldN.extras == null || newN.extras == null) {
if (DEBUG_INTERRUPTIVENESS) {
Slog.v(TAG, "INTERRUPTIVENESS: "
@@ -5890,6 +5891,7 @@
}
return true;
}
+
// Do not compare Spannables (will always return false); compare unstyled Strings
final String oldText = String.valueOf(oldN.extras.get(Notification.EXTRA_TEXT));
final String newText = String.valueOf(newN.extras.get(Notification.EXTRA_TEXT));
@@ -5904,6 +5906,7 @@
}
return true;
}
+
if (oldN.hasCompletedProgress() != newN.hasCompletedProgress()) {
if (DEBUG_INTERRUPTIVENESS) {
Slog.v(TAG, "INTERRUPTIVENESS: "
@@ -5911,6 +5914,16 @@
}
return true;
}
+
+ // Fields below are invisible to bubbles.
+ if (r.canBubble()) {
+ if (DEBUG_INTERRUPTIVENESS) {
+ Slog.v(TAG, "INTERRUPTIVENESS: "
+ + r.getKey() + " is not interruptive: bubble");
+ }
+ return false;
+ }
+
// Actions
if (Notification.areActionsVisiblyDifferent(oldN, newN)) {
if (DEBUG_INTERRUPTIVENESS) {
@@ -5944,7 +5957,6 @@
} catch (Exception e) {
Slog.w(TAG, "error recovering builder", e);
}
-
return false;
}
@@ -6139,12 +6151,17 @@
Slog.v(TAG, "INTERRUPTIVENESS: "
+ record.getKey() + " is not interruptive: summary");
}
+ } else if (record.canBubble()) {
+ if (DEBUG_INTERRUPTIVENESS) {
+ Slog.v(TAG, "INTERRUPTIVENESS: "
+ + record.getKey() + " is not interruptive: bubble");
+ }
} else {
+ record.setInterruptive(true);
if (DEBUG_INTERRUPTIVENESS) {
Slog.v(TAG, "INTERRUPTIVENESS: "
+ record.getKey() + " is interruptive: alerted");
}
- record.setInterruptive(true);
}
MetricsLogger.action(record.getLogMaker()
.setCategory(MetricsEvent.NOTIFICATION_ALERT)
@@ -6503,15 +6520,21 @@
int indexBefore = findNotificationRecordIndexLocked(record);
boolean interceptBefore = record.isIntercepted();
int visibilityBefore = record.getPackageVisibilityOverride();
+ boolean interruptiveBefore = record.isInterruptive();
+
recon.applyChangesLocked(record);
applyZenModeLocked(record);
mRankingHelper.sort(mNotificationList);
- int indexAfter = findNotificationRecordIndexLocked(record);
- boolean interceptAfter = record.isIntercepted();
- int visibilityAfter = record.getPackageVisibilityOverride();
- changed = indexBefore != indexAfter || interceptBefore != interceptAfter
- || visibilityBefore != visibilityAfter;
- if (interceptBefore && !interceptAfter
+ boolean indexChanged = indexBefore != findNotificationRecordIndexLocked(record);
+ boolean interceptChanged = interceptBefore != record.isIntercepted();
+ boolean visibilityChanged = visibilityBefore != record.getPackageVisibilityOverride();
+
+ // Broadcast isInterruptive changes for bubbles.
+ boolean interruptiveChanged =
+ record.canBubble() && (interruptiveBefore != record.isInterruptive());
+
+ changed = indexChanged || interceptChanged || visibilityChanged || interruptiveChanged;
+ if (interceptBefore && !record.isIntercepted()
&& record.isNewEnoughForAlerting(System.currentTimeMillis())) {
buzzBeepBlinkLocked(record);
}
@@ -7661,7 +7684,8 @@
record.getSound() != null || record.getVibration() != null,
record.getSystemGeneratedSmartActions(),
record.getSmartReplies(),
- record.canBubble()
+ record.canBubble(),
+ record.isInterruptive()
);
rankings.add(ranking);
}
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index 48678bf..61ea84f 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -39,6 +39,7 @@
import android.util.SparseArray;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.FgThread;
import com.android.server.compat.PlatformCompat;
@@ -55,14 +56,11 @@
* The entity responsible for filtering visibility between apps based on declarations in their
* manifests.
*/
-class AppsFilter {
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class AppsFilter {
private static final String TAG = PackageManagerService.TAG;
- // Forces filtering logic to run for debug purposes.
- // STOPSHIP (b/136675067): should be false after development is complete
- private static final boolean DEBUG_RUN_WHEN_DISABLED = false;
-
// Logs all filtering instead of enforcing
private static final boolean DEBUG_ALLOW_ALL = false;
@@ -128,15 +126,13 @@
/** @return true if the feature is enabled for the given package. */
boolean packageIsEnabled(PackageParser.Package pkg);
+
}
private static class FeatureConfigImpl implements FeatureConfig {
private static final String FILTERING_ENABLED_NAME = "package_query_filtering_enabled";
-
- // STOPSHIP(patb): set this to true if we plan to launch this in R
- private static final boolean DEFAULT_ENABLED_STATE = false;
private final PackageManagerService.Injector mInjector;
- private volatile boolean mFeatureEnabled = DEFAULT_ENABLED_STATE;
+ private volatile boolean mFeatureEnabled = true;
private FeatureConfigImpl(PackageManagerService.Injector injector) {
mInjector = injector;
@@ -146,13 +142,13 @@
public void onSystemReady() {
mFeatureEnabled = DeviceConfig.getBoolean(
NAMESPACE_PACKAGE_MANAGER_SERVICE, FILTERING_ENABLED_NAME,
- DEFAULT_ENABLED_STATE);
+ true);
DeviceConfig.addOnPropertiesChangedListener(
NAMESPACE_PACKAGE_MANAGER_SERVICE, FgThread.getExecutor(),
properties -> {
synchronized (FeatureConfigImpl.this) {
mFeatureEnabled = properties.getBoolean(
- FILTERING_ENABLED_NAME, DEFAULT_ENABLED_STATE);
+ FILTERING_ENABLED_NAME, true);
}
});
}
@@ -246,25 +242,25 @@
}
/**
- * Marks that a package initiated an interaction with another package, granting visibility of
- * the prior from the former.
+ * Grants access based on an interaction between a calling and target package, granting
+ * visibility of the caller from the target.
*
- * @param initiatingPackage the package initiating the interaction
+ * @param callingPackage the package initiating the interaction
* @param targetPackage the package being interacted with and thus gaining visibility of the
* initiating package.
* @param userId the user in which this interaction was taking place
*/
- private void markAppInteraction(
- PackageSetting initiatingPackage, PackageSetting targetPackage, int userId) {
+ public void grantImplicitAccess(
+ String callingPackage, String targetPackage, int userId) {
HashMap<String, Set<String>> currentUser = mImplicitlyQueryable.get(userId);
if (currentUser == null) {
currentUser = new HashMap<>();
mImplicitlyQueryable.put(userId, currentUser);
}
- if (!currentUser.containsKey(targetPackage.pkg.packageName)) {
- currentUser.put(targetPackage.pkg.packageName, new HashSet<>());
+ if (!currentUser.containsKey(targetPackage)) {
+ currentUser.put(targetPackage, new HashSet<>());
}
- currentUser.get(targetPackage.pkg.packageName).add(initiatingPackage.pkg.packageName);
+ currentUser.get(targetPackage).add(callingPackage);
}
public void onSystemReady() {
@@ -353,7 +349,7 @@
public boolean shouldFilterApplication(int callingUid, @Nullable SettingBase callingSetting,
PackageSetting targetPkgSetting, int userId) {
final boolean featureEnabled = mFeatureConfig.isGloballyEnabled();
- if (!featureEnabled && !DEBUG_RUN_WHEN_DISABLED) {
+ if (!featureEnabled) {
if (DEBUG_LOGGING) {
Slog.d(TAG, "filtering disabled; skipped");
}
@@ -374,8 +370,6 @@
callingPkgSetting = (PackageSetting) callingSetting;
if (!shouldFilterApplicationInternal(callingPkgSetting, targetPkgSetting,
userId)) {
- // TODO: actually base this on a start / launch (not just a query)
- markAppInteraction(callingPkgSetting, targetPkgSetting, userId);
return false;
}
} else if (callingSetting instanceof SharedUserSetting) {
@@ -386,8 +380,6 @@
final PackageSetting packageSetting = packageSettings.valueAt(i);
if (!shouldFilterApplicationInternal(packageSetting, targetPkgSetting,
userId)) {
- // TODO: actually base this on a start / launch (not just a query)
- markAppInteraction(packageSetting, targetPkgSetting, userId);
return false;
}
if (callingPkgSetting == null && packageSetting.pkg != null) {
@@ -403,21 +395,12 @@
return true;
}
}
- if (!featureEnabled) {
- return false;
+
+ if (DEBUG_LOGGING) {
+ log(callingPkgSetting, targetPkgSetting,
+ DEBUG_ALLOW_ALL ? "ALLOWED" : "BLOCKED");
}
- if (mFeatureConfig.packageIsEnabled(callingPkgSetting.pkg)) {
- if (DEBUG_LOGGING) {
- log(callingPkgSetting, targetPkgSetting,
- DEBUG_ALLOW_ALL ? "ALLOWED" : "BLOCKED");
- }
- return !DEBUG_ALLOW_ALL;
- } else {
- if (DEBUG_LOGGING) {
- log(callingPkgSetting, targetPkgSetting, "DISABLED");
- }
- return false;
- }
+ return !DEBUG_ALLOW_ALL;
}
private boolean shouldFilterApplicationInternal(
@@ -425,6 +408,12 @@
final String callingName = callingPkgSetting.pkg.packageName;
final PackageParser.Package targetPkg = targetPkgSetting.pkg;
+ if (!mFeatureConfig.packageIsEnabled(callingPkgSetting.pkg)) {
+ if (DEBUG_LOGGING) {
+ log(callingPkgSetting, targetPkgSetting, "DISABLED");
+ }
+ return false;
+ }
// This package isn't technically installed and won't be written to settings, so we can
// treat it as filtered until it's available again.
if (targetPkg == null) {
diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java
index 5eaddf9..9e04c4b 100644
--- a/services/core/java/com/android/server/pm/InstantAppRegistry.java
+++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java
@@ -401,7 +401,7 @@
@GuardedBy("mService.mLock")
public void grantInstantAccessLPw(@UserIdInt int userId, @Nullable Intent intent,
- int targetAppId, int instantAppId) {
+ int instantAppId, int targetAppId) {
if (mInstalledInstantAppUids == null) {
return; // no instant apps installed; no need to grant
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 58596aa..fe3d2c8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -23380,11 +23380,24 @@
}
@Override
- public void grantEphemeralAccess(int userId, Intent intent,
- int targetAppId, int ephemeralAppId) {
+ public void grantImplicitAccess(int userId, Intent intent,
+ int callingAppId, int targetAppId) {
synchronized (mLock) {
- mInstantAppRegistry.grantInstantAccessLPw(userId, intent,
- targetAppId, ephemeralAppId);
+ final PackageParser.Package callingPackage = getPackage(
+ UserHandle.getUid(userId, callingAppId));
+ final PackageParser.Package targetPackage = getPackage(
+ UserHandle.getUid(userId, targetAppId));
+ if (callingPackage == null || targetPackage == null) {
+ return;
+ }
+
+ if (isInstantApp(callingPackage.packageName, userId)) {
+ mInstantAppRegistry.grantInstantAccessLPw(userId, intent,
+ callingAppId, targetAppId);
+ } else {
+ mAppsFilter.grantImplicitAccess(
+ callingPackage.packageName, targetPackage.packageName, userId);
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 3482e92..1cea4ca 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -301,7 +301,7 @@
// Greedily re-trigger the pre-reboot verification.
Slog.d(TAG, "Found pending staged session " + session.sessionId + " still to be "
+ "verified, resuming pre-reboot verification");
- mPreRebootVerificationHandler.startPreRebootVerification(session);
+ mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId);
return;
}
if (!apexSessionInfo.isActivated && !apexSessionInfo.isSuccess) {
@@ -429,7 +429,7 @@
return;
}
mPreRebootVerificationHandler.notifyPreRebootVerification_Apk_Complete(
- originalSession);
+ originalSession.sessionId);
});
apkSession.commit(receiver.getIntentSender(), false);
return;
@@ -526,7 +526,7 @@
void commitSession(@NonNull PackageInstallerSession session) {
updateStoredSession(session);
- mPreRebootVerificationHandler.startPreRebootVerification(session);
+ mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId);
}
@Nullable
@@ -653,7 +653,7 @@
if (!session.isStagedSessionReady()) {
// The framework got restarted before the pre-reboot verification could complete,
// restart the verification.
- mPreRebootVerificationHandler.startPreRebootVerification(session);
+ mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId);
} else {
// Session had already being marked ready. Start the checks to verify if there is any
// follow-up work.
@@ -737,7 +737,16 @@
@Override
public void handleMessage(Message msg) {
- PackageInstallerSession session = (PackageInstallerSession) msg.obj;
+ final int sessionId = msg.arg1;
+ final PackageInstallerSession session;
+ synchronized (mStagedSessions) {
+ session = mStagedSessions.get(sessionId);
+ }
+ // Maybe session was aborted before pre-reboot verification was complete
+ if (session == null) {
+ Slog.d(TAG, "Stopping pre-reboot verification for sessionId: " + sessionId);
+ return;
+ }
switch (msg.what) {
case MSG_PRE_REBOOT_VERIFICATION_START:
handlePreRebootVerification_Start(session);
@@ -755,20 +764,20 @@
}
// Method for starting the pre-reboot verification
- private void startPreRebootVerification(PackageInstallerSession session) {
- obtainMessage(MSG_PRE_REBOOT_VERIFICATION_START, session).sendToTarget();
+ private void startPreRebootVerification(int sessionId) {
+ obtainMessage(MSG_PRE_REBOOT_VERIFICATION_START, sessionId, 0).sendToTarget();
}
- private void notifyPreRebootVerification_Start_Complete(PackageInstallerSession session) {
- obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APEX, session).sendToTarget();
+ private void notifyPreRebootVerification_Start_Complete(int sessionId) {
+ obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APEX, sessionId, 0).sendToTarget();
}
- private void notifyPreRebootVerification_Apex_Complete(PackageInstallerSession session) {
- obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APK, session).sendToTarget();
+ private void notifyPreRebootVerification_Apex_Complete(int sessionId) {
+ obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APK, sessionId, 0).sendToTarget();
}
- private void notifyPreRebootVerification_Apk_Complete(PackageInstallerSession session) {
- obtainMessage(MSG_PRE_REBOOT_VERIFICATION_END, session).sendToTarget();
+ private void notifyPreRebootVerification_Apk_Complete(int sessionId) {
+ obtainMessage(MSG_PRE_REBOOT_VERIFICATION_END, sessionId, 0).sendToTarget();
}
/**
@@ -778,7 +787,7 @@
*/
private void handlePreRebootVerification_Start(@NonNull PackageInstallerSession session) {
Slog.d(TAG, "Starting preRebootVerification for session " + session.sessionId);
- notifyPreRebootVerification_Start_Complete(session);
+ notifyPreRebootVerification_Start_Complete(session.sessionId);
}
/**
@@ -808,7 +817,7 @@
}
}
- notifyPreRebootVerification_Apex_Complete(session);
+ notifyPreRebootVerification_Apex_Complete(session.sessionId);
}
/**
@@ -819,7 +828,7 @@
*/
private void handlePreRebootVerification_Apk(@NonNull PackageInstallerSession session) {
if (!sessionContainsApk(session)) {
- notifyPreRebootVerification_Apk_Complete(session);
+ notifyPreRebootVerification_Apk_Complete(session.sessionId);
return;
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 226adc8..a57321e 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -793,62 +793,68 @@
final CheckPermissionDelegate checkPermissionDelegate;
synchronized (mLock) {
- if (mCheckPermissionDelegate == null) {
- return checkPermissionImpl(permName, pkgName, userId);
- }
checkPermissionDelegate = mCheckPermissionDelegate;
}
+ if (checkPermissionDelegate == null) {
+ return checkPermissionImpl(permName, pkgName, userId);
+ }
return checkPermissionDelegate.checkPermission(permName, pkgName, userId,
- PermissionManagerService.this::checkPermissionImpl);
+ this::checkPermissionImpl);
}
- private int checkPermissionImpl(String permName, String pkgName, int userId) {
- final PackageParser.Package pkg = mPackageManagerInt.getPackage(pkgName);
+ private int checkPermissionImpl(@NonNull String permissionName, @NonNull String packageName,
+ @UserIdInt int userId) {
+ final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
if (pkg == null) {
return PackageManager.PERMISSION_DENIED;
}
- return checkPermissionInternal(pkg, true, permName, userId);
+ return checkPermissionInternal(pkg, true, permissionName, true, userId)
+ ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED;
}
- private int checkPermissionInternal(@NonNull Package pkg, boolean isPackageExplicit,
- @NonNull String permissionName, @UserIdInt int userId) {
+ private boolean checkPermissionInternal(@NonNull Package pkg, boolean isPackageExplicit,
+ @NonNull String permissionName, boolean useRequestedPermissionsForLegacyApps,
+ @UserIdInt int userId) {
final int callingUid = getCallingUid();
if (isPackageExplicit || pkg.mSharedUserId == null) {
if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
- return PackageManager.PERMISSION_DENIED;
+ return false;
}
} else {
if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
- return PackageManager.PERMISSION_DENIED;
+ return false;
}
}
final int uid = UserHandle.getUid(userId, pkg.applicationInfo.uid);
final PackageSetting ps = (PackageSetting) pkg.mExtras;
if (ps == null) {
- return PackageManager.PERMISSION_DENIED;
+ return false;
}
final PermissionsState permissionsState = ps.getPermissionsState();
- if (checkSinglePermissionInternal(uid, permissionsState, permissionName)) {
- return PackageManager.PERMISSION_GRANTED;
+ if (checkSinglePermissionInternal(uid, permissionsState, permissionName,
+ useRequestedPermissionsForLegacyApps)) {
+ return true;
}
final String fullerPermissionName = FULLER_PERMISSION_MAP.get(permissionName);
- if (fullerPermissionName != null
- && checkSinglePermissionInternal(uid, permissionsState, fullerPermissionName)) {
- return PackageManager.PERMISSION_GRANTED;
+ if (fullerPermissionName != null && checkSinglePermissionInternal(uid, permissionsState,
+ fullerPermissionName, useRequestedPermissionsForLegacyApps)) {
+ return true;
}
- return PackageManager.PERMISSION_DENIED;
+ return false;
}
private boolean checkSinglePermissionInternal(int uid,
- @NonNull PermissionsState permissionsState, @NonNull String permissionName) {
+ @NonNull PermissionsState permissionsState, @NonNull String permissionName,
+ boolean useRequestedPermissionsForLegacyApps) {
boolean hasPermission = permissionsState.hasPermission(permissionName,
UserHandle.getUserId(uid));
- if (!hasPermission && mSettings.isPermissionRuntime(permissionName)) {
+ if (!hasPermission && useRequestedPermissionsForLegacyApps
+ && mSettings.isPermissionRuntime(permissionName)) {
final String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
final int packageNamesSize = packageNames != null ? packageNames.length : 0;
for (int i = 0; i < packageNamesSize; i++) {
@@ -891,12 +897,13 @@
checkPermissionDelegate = mCheckPermissionDelegate;
}
return checkPermissionDelegate.checkUidPermission(permName, uid,
- PermissionManagerService.this::checkUidPermissionImpl);
+ this::checkUidPermissionImpl);
}
- private int checkUidPermissionImpl(String permName, int uid) {
+ private int checkUidPermissionImpl(@NonNull String permissionName, int uid) {
final PackageParser.Package pkg = mPackageManagerInt.getPackage(uid);
- return checkUidPermissionInternal(pkg, uid, permName);
+ return checkUidPermissionInternal(uid, pkg, permissionName, true)
+ ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED;
}
/**
@@ -906,24 +913,25 @@
*
* @see SystemConfig#getSystemPermissions()
*/
- private int checkUidPermissionInternal(@Nullable Package pkg, int uid,
- @NonNull String permissionName) {
+ private boolean checkUidPermissionInternal(int uid, @Nullable Package pkg,
+ @NonNull String permissionName, boolean useRequestedPermissionsForLegacyApps) {
if (pkg != null) {
final int userId = UserHandle.getUserId(uid);
- return checkPermissionInternal(pkg, false, permissionName, userId);
+ return checkPermissionInternal(pkg, false, permissionName,
+ useRequestedPermissionsForLegacyApps, userId);
}
if (checkSingleUidPermissionInternal(uid, permissionName)) {
- return PackageManager.PERMISSION_GRANTED;
+ return true;
}
final String fullerPermissionName = FULLER_PERMISSION_MAP.get(permissionName);
if (fullerPermissionName != null
&& checkSingleUidPermissionInternal(uid, fullerPermissionName)) {
- return PackageManager.PERMISSION_GRANTED;
+ return true;
}
- return PackageManager.PERMISSION_DENIED;
+ return false;
}
private boolean checkSingleUidPermissionInternal(int uid, @NonNull String permissionName) {
@@ -933,6 +941,17 @@
}
}
+ private int computeRuntimePermissionAppOpMode(int uid, @NonNull String permissionName) {
+ boolean granted = isUidPermissionGranted(uid, permissionName);
+ // TODO: Foreground permissions.
+ return granted ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED;
+ }
+
+ private boolean isUidPermissionGranted(int uid, @NonNull String permissionName) {
+ final PackageParser.Package pkg = mPackageManagerInt.getPackage(uid);
+ return checkUidPermissionInternal(uid, pkg, permissionName, false);
+ }
+
@Override
public void addOnPermissionsChangeListener(IOnPermissionsChangeListener listener) {
mContext.enforceCallingOrSelfPermission(
@@ -4283,7 +4302,6 @@
public void setCheckPermissionDelegate(CheckPermissionDelegate delegate) {
synchronized (mLock) {
mCheckPermissionDelegate = delegate;
- Slog.d(TAG, "CheckPermissionDelegate set to " + delegate);
}
}
@@ -4447,6 +4465,12 @@
StorageManager.UUID_PRIVATE_INTERNAL, true, mDefaultPermissionCallback);
}
}
+
+ @Override
+ public int computeRuntimePermissionAppOpMode(int uid, @NonNull String permissionName) {
+ return PermissionManagerService.this.computeRuntimePermissionAppOpMode(uid,
+ permissionName);
+ }
}
private static final class OnPermissionChangeListeners extends Handler {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index 04ec5ba..8f22f92 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -445,4 +445,13 @@
/** Called when a new user has been created. */
public abstract void onNewUserCreated(@UserIdInt int userId);
+
+ /**
+ * Compute an app op mode based on its runtime permission state.
+ *
+ * @param uid the uid for the app op
+ * @param permissionName the permission name for the app op
+ * @return the computed mode
+ */
+ public abstract int computeRuntimePermissionAppOpMode(int uid, @NonNull String permissionName);
}
diff --git a/services/core/java/com/android/server/stats/ProcfsMemoryUtil.java b/services/core/java/com/android/server/stats/ProcfsMemoryUtil.java
new file mode 100644
index 0000000..3076284
--- /dev/null
+++ b/services/core/java/com/android/server/stats/ProcfsMemoryUtil.java
@@ -0,0 +1,75 @@
+/*
+ * 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.stats;
+
+import android.os.FileUtils;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+final class ProcfsMemoryUtil {
+ private static final String TAG = "ProcfsMemoryUtil";
+
+ /** Path to procfs status file: /proc/pid/status. */
+ private static final String STATUS_FILE_FMT = "/proc/%d/status";
+
+ private static final Pattern RSS_HIGH_WATER_MARK_IN_KILOBYTES =
+ Pattern.compile("VmHWM:\\s*(\\d+)\\s*kB");
+
+ private ProcfsMemoryUtil() {}
+
+ /**
+ * Reads RSS high-water mark of a process from procfs. Returns value of the VmHWM field in
+ * /proc/PID/status in kilobytes or 0 if not available.
+ */
+ static int readRssHighWaterMarkFromProcfs(int pid) {
+ final String statusPath = String.format(Locale.US, STATUS_FILE_FMT, pid);
+ return parseVmHWMFromStatus(readFile(statusPath));
+ }
+
+ /**
+ * Parses RSS high-water mark out from the contents of the /proc/pid/status file in procfs. The
+ * returned value is in kilobytes.
+ */
+ @VisibleForTesting
+ static int parseVmHWMFromStatus(String contents) {
+ if (contents.isEmpty()) {
+ return 0;
+ }
+ final Matcher matcher = RSS_HIGH_WATER_MARK_IN_KILOBYTES.matcher(contents);
+ try {
+ return matcher.find() ? Integer.parseInt(matcher.group(1)) : 0;
+ } catch (NumberFormatException e) {
+ Slog.e(TAG, "Failed to parse value", e);
+ return 0;
+ }
+ }
+
+ private static String readFile(String path) {
+ try {
+ final File file = new File(path);
+ return FileUtils.readTextFile(file, 0 /* max */, null /* ellipsis */);
+ } catch (IOException e) {
+ return "";
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index e92abfd..19b8055 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -27,9 +27,9 @@
import static com.android.server.am.MemoryStatUtil.readCmdlineFromProcfs;
import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
import static com.android.server.am.MemoryStatUtil.readMemoryStatFromProcfs;
-import static com.android.server.am.MemoryStatUtil.readRssHighWaterMarkFromProcfs;
import static com.android.server.stats.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
import static com.android.server.stats.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
+import static com.android.server.stats.ProcfsMemoryUtil.readRssHighWaterMarkFromProcfs;
import android.annotation.NonNull;
import android.annotation.Nullable;
diff --git a/services/core/java/com/android/server/tv/TvRemoteProviderProxy.java b/services/core/java/com/android/server/tv/TvRemoteProviderProxy.java
index b27d5ea..f8ffb7c 100644
--- a/services/core/java/com/android/server/tv/TvRemoteProviderProxy.java
+++ b/services/core/java/com/android/server/tv/TvRemoteProviderProxy.java
@@ -23,7 +23,6 @@
import android.media.tv.ITvRemoteProvider;
import android.media.tv.ITvRemoteServiceInput;
import android.os.Binder;
-import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -49,7 +48,6 @@
private final ComponentName mComponentName;
private final int mUserId;
private final int mUid;
- private final Handler mHandler;
/**
* State guarded by mLock.
@@ -65,15 +63,14 @@
private boolean mRunning;
private boolean mBound;
private Connection mActiveConnection;
- private boolean mConnectionReady;
- public TvRemoteProviderProxy(Context context, ComponentName componentName, int userId,
- int uid) {
+ TvRemoteProviderProxy(Context context, ProviderMethods provider,
+ ComponentName componentName, int userId, int uid) {
mContext = context;
+ mProviderMethods = provider;
mComponentName = componentName;
mUserId = userId;
mUid = uid;
- mHandler = new Handler();
}
public void dump(PrintWriter pw, String prefix) {
@@ -82,11 +79,6 @@
pw.println(prefix + " mRunning=" + mRunning);
pw.println(prefix + " mBound=" + mBound);
pw.println(prefix + " mActiveConnection=" + mActiveConnection);
- pw.println(prefix + " mConnectionReady=" + mConnectionReady);
- }
-
- public void setProviderSink(ProviderMethods provider) {
- mProviderMethods = provider;
}
public boolean hasComponentName(String packageName, String className) {
@@ -101,7 +93,7 @@
}
mRunning = true;
- updateBinding();
+ bind();
}
}
@@ -112,31 +104,19 @@
}
mRunning = false;
- updateBinding();
+ unbind();
}
}
public void rebindIfDisconnected() {
synchronized (mLock) {
- if (mActiveConnection == null && shouldBind()) {
+ if (mActiveConnection == null && mRunning) {
unbind();
bind();
}
}
}
- private void updateBinding() {
- if (shouldBind()) {
- bind();
- } else {
- unbind();
- }
- }
-
- private boolean shouldBind() {
- return mRunning;
- }
-
private void bind() {
if (!mBound) {
if (DEBUG) {
@@ -208,48 +188,19 @@
disconnect();
}
-
- private void onConnectionReady(Connection connection) {
- synchronized (mLock) {
- if (DEBUG) Slog.d(TAG, "onConnectionReady");
- if (mActiveConnection == connection) {
- if (DEBUG) Slog.d(TAG, "mConnectionReady = true");
- mConnectionReady = true;
- }
- }
- }
-
- private void onConnectionDied(Connection connection) {
- if (mActiveConnection == connection) {
- if (DEBUG) Slog.d(TAG, this + ": Service connection died");
- disconnect();
- }
- }
-
private void disconnect() {
synchronized (mLock) {
if (mActiveConnection != null) {
- mConnectionReady = false;
mActiveConnection.dispose();
mActiveConnection = null;
}
}
}
- // Provider helpers
- public void inputBridgeConnected(IBinder token) {
- synchronized (mLock) {
- if (DEBUG) Slog.d(TAG, this + ": inputBridgeConnected token: " + token);
- if (mConnectionReady) {
- mActiveConnection.onInputBridgeConnected(token);
- }
- }
- }
-
- public interface ProviderMethods {
+ interface ProviderMethods {
// InputBridge
- void openInputBridge(TvRemoteProviderProxy provider, IBinder token, String name,
- int width, int height, int maxPointers);
+ boolean openInputBridge(TvRemoteProviderProxy provider, IBinder token, String name,
+ int width, int height, int maxPointers);
void closeInputBridge(TvRemoteProviderProxy provider, IBinder token);
@@ -267,7 +218,7 @@
void sendPointerSync(TvRemoteProviderProxy provider, IBinder token);
}
- private final class Connection implements IBinder.DeathRecipient {
+ private final class Connection {
private final ITvRemoteProvider mTvRemoteProvider;
private final RemoteServiceInputProvider mServiceInputProvider;
@@ -279,24 +230,16 @@
public boolean register() {
if (DEBUG) Slog.d(TAG, "Connection::register()");
try {
- mTvRemoteProvider.asBinder().linkToDeath(this, 0);
mTvRemoteProvider.setRemoteServiceInputSink(mServiceInputProvider);
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- onConnectionReady(Connection.this);
- }
- });
return true;
} catch (RemoteException ex) {
- binderDied();
+ dispose();
+ return false;
}
- return false;
}
public void dispose() {
if (DEBUG) Slog.d(TAG, "Connection::dispose()");
- mTvRemoteProvider.asBinder().unlinkToDeath(this, 0);
mServiceInputProvider.dispose();
}
@@ -310,16 +253,6 @@
}
}
- @Override
- public void binderDied() {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- onConnectionDied(Connection.this);
- }
- });
- }
-
void openInputBridge(final IBinder token, final String name, final int width,
final int height, final int maxPointers) {
synchronized (mLock) {
@@ -330,9 +263,9 @@
}
final long idToken = Binder.clearCallingIdentity();
try {
- if (mProviderMethods != null) {
- mProviderMethods.openInputBridge(TvRemoteProviderProxy.this, token,
- name, width, height, maxPointers);
+ if (mProviderMethods.openInputBridge(TvRemoteProviderProxy.this, token,
+ name, width, height, maxPointers)) {
+ onInputBridgeConnected(token);
}
} finally {
Binder.restoreCallingIdentity(idToken);
@@ -356,9 +289,7 @@
}
final long idToken = Binder.clearCallingIdentity();
try {
- if (mProviderMethods != null) {
- mProviderMethods.closeInputBridge(TvRemoteProviderProxy.this, token);
- }
+ mProviderMethods.closeInputBridge(TvRemoteProviderProxy.this, token);
} finally {
Binder.restoreCallingIdentity(idToken);
}
@@ -381,9 +312,7 @@
}
final long idToken = Binder.clearCallingIdentity();
try {
- if (mProviderMethods != null) {
- mProviderMethods.clearInputBridge(TvRemoteProviderProxy.this, token);
- }
+ mProviderMethods.clearInputBridge(TvRemoteProviderProxy.this, token);
} finally {
Binder.restoreCallingIdentity(idToken);
}
@@ -412,10 +341,7 @@
}
final long idToken = Binder.clearCallingIdentity();
try {
- if (mProviderMethods != null) {
- mProviderMethods.sendKeyDown(TvRemoteProviderProxy.this, token,
- keyCode);
- }
+ mProviderMethods.sendKeyDown(TvRemoteProviderProxy.this, token, keyCode);
} finally {
Binder.restoreCallingIdentity(idToken);
}
@@ -438,9 +364,7 @@
}
final long idToken = Binder.clearCallingIdentity();
try {
- if (mProviderMethods != null) {
- mProviderMethods.sendKeyUp(TvRemoteProviderProxy.this, token, keyCode);
- }
+ mProviderMethods.sendKeyUp(TvRemoteProviderProxy.this, token, keyCode);
} finally {
Binder.restoreCallingIdentity(idToken);
}
@@ -463,10 +387,8 @@
}
final long idToken = Binder.clearCallingIdentity();
try {
- if (mProviderMethods != null) {
- mProviderMethods.sendPointerDown(TvRemoteProviderProxy.this, token,
- pointerId, x, y);
- }
+ mProviderMethods.sendPointerDown(TvRemoteProviderProxy.this, token,
+ pointerId, x, y);
} finally {
Binder.restoreCallingIdentity(idToken);
}
@@ -489,10 +411,8 @@
}
final long idToken = Binder.clearCallingIdentity();
try {
- if (mProviderMethods != null) {
- mProviderMethods.sendPointerUp(TvRemoteProviderProxy.this, token,
- pointerId);
- }
+ mProviderMethods.sendPointerUp(TvRemoteProviderProxy.this, token,
+ pointerId);
} finally {
Binder.restoreCallingIdentity(idToken);
}
diff --git a/services/core/java/com/android/server/tv/TvRemoteProviderWatcher.java b/services/core/java/com/android/server/tv/TvRemoteProviderWatcher.java
index d27970f..0d29edd 100644
--- a/services/core/java/com/android/server/tv/TvRemoteProviderWatcher.java
+++ b/services/core/java/com/android/server/tv/TvRemoteProviderWatcher.java
@@ -45,7 +45,7 @@
private static final boolean DEBUG = Log.isLoggable(TAG, Log.VERBOSE);
private final Context mContext;
- private final ProviderMethods mProvider;
+ private final TvRemoteProviderProxy.ProviderMethods mProvider;
private final Handler mHandler;
private final PackageManager mPackageManager;
private final ArrayList<TvRemoteProviderProxy> mProviderProxies = new ArrayList<>();
@@ -54,10 +54,10 @@
private boolean mRunning;
- public TvRemoteProviderWatcher(Context context, ProviderMethods provider, Handler handler) {
+ TvRemoteProviderWatcher(Context context, TvRemoteProviderProxy.ProviderMethods provider) {
mContext = context;
mProvider = provider;
- mHandler = handler;
+ mHandler = new Handler(true);
mUserId = UserHandle.myUserId();
mPackageManager = context.getPackageManager();
mUnbundledServicePackage = context.getString(
@@ -116,12 +116,11 @@
int sourceIndex = findProvider(serviceInfo.packageName, serviceInfo.name);
if (sourceIndex < 0) {
TvRemoteProviderProxy providerProxy =
- new TvRemoteProviderProxy(mContext,
+ new TvRemoteProviderProxy(mContext, mProvider,
new ComponentName(serviceInfo.packageName, serviceInfo.name),
mUserId, serviceInfo.applicationInfo.uid);
providerProxy.start();
mProviderProxies.add(targetIndex++, providerProxy);
- mProvider.addProvider(providerProxy);
} else if (sourceIndex >= targetIndex) {
TvRemoteProviderProxy provider = mProviderProxies.get(sourceIndex);
provider.start(); // restart the provider if needed
@@ -135,7 +134,6 @@
if (targetIndex < mProviderProxies.size()) {
for (int i = mProviderProxies.size() - 1; i >= targetIndex; i--) {
TvRemoteProviderProxy providerProxy = mProviderProxies.get(i);
- mProvider.removeProvider(providerProxy);
mProviderProxies.remove(providerProxy);
providerProxy.stop();
}
@@ -212,10 +210,4 @@
scanPackages();
}
};
-
- public interface ProviderMethods {
- void addProvider(TvRemoteProviderProxy providerProxy);
-
- void removeProvider(TvRemoteProviderProxy providerProxy);
- }
}
diff --git a/services/core/java/com/android/server/tv/TvRemoteService.java b/services/core/java/com/android/server/tv/TvRemoteService.java
index ba74791..bee6fb3 100644
--- a/services/core/java/com/android/server/tv/TvRemoteService.java
+++ b/services/core/java/com/android/server/tv/TvRemoteService.java
@@ -17,11 +17,8 @@
package com.android.server.tv;
import android.content.Context;
-import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
-import android.os.Looper;
-import android.os.Message;
import android.util.ArrayMap;
import android.util.Slog;
@@ -29,7 +26,6 @@
import com.android.server.Watchdog;
import java.io.IOException;
-import java.util.ArrayList;
import java.util.Map;
/**
@@ -44,9 +40,8 @@
private static final boolean DEBUG = false;
private static final boolean DEBUG_KEYS = false;
+ private final TvRemoteProviderWatcher mWatcher;
private Map<IBinder, UinputBridge> mBridgeMap = new ArrayMap();
- private Map<IBinder, TvRemoteProviderProxy> mProviderMap = new ArrayMap();
- private ArrayList<TvRemoteProviderProxy> mProviderList = new ArrayList<>();
/**
* State guarded by mLock.
@@ -60,11 +55,10 @@
*/
private final Object mLock = new Object();
- public final UserHandler mHandler;
-
public TvRemoteService(Context context) {
super(context);
- mHandler = new UserHandler(new UserProvider(TvRemoteService.this), context);
+ mWatcher = new TvRemoteProviderWatcher(context,
+ new UserProvider(TvRemoteService.this));
Watchdog.getInstance().addMonitor(this);
}
@@ -80,21 +74,17 @@
@Override
public void onBootPhase(int phase) {
+ // All lifecycle methods are called from the system server's main looper thread.
if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
if (DEBUG) Slog.d(TAG, "PHASE_THIRD_PARTY_APPS_CAN_START");
- mHandler.sendEmptyMessage(UserHandler.MSG_START);
+
+ mWatcher.start(); // Also schedules the start of all providers.
}
}
- //Outgoing calls.
- private void informInputBridgeConnected(IBinder token) {
- mHandler.obtainMessage(UserHandler.MSG_INPUT_BRIDGE_CONNECTED, 0, 0, token).sendToTarget();
- }
-
- // Incoming calls.
- private void openInputBridgeInternalLocked(TvRemoteProviderProxy provider, final IBinder token,
- String name, int width, int height,
- int maxPointers) {
+ private boolean openInputBridgeInternalLocked(final IBinder token,
+ String name, int width, int height,
+ int maxPointers) {
if (DEBUG) {
Slog.d(TAG, "openInputBridgeInternalLocked(), token: " + token + ", name: " + name +
", width: " + width + ", height: " + height + ", maxPointers: " + maxPointers);
@@ -104,15 +94,11 @@
//Create a new bridge, if one does not exist already
if (mBridgeMap.containsKey(token)) {
if (DEBUG) Slog.d(TAG, "RemoteBridge already exists");
- // Respond back with success.
- informInputBridgeConnected(token);
- return;
+ return true;
}
UinputBridge inputBridge = new UinputBridge(token, name, width, height, maxPointers);
-
mBridgeMap.put(token, inputBridge);
- mProviderMap.put(token, provider);
try {
token.linkToDeath(new IBinder.DeathRecipient() {
@@ -126,15 +112,13 @@
} catch (RemoteException e) {
if (DEBUG) Slog.d(TAG, "Token is already dead");
closeInputBridgeInternalLocked(token);
- return;
+ return false;
}
-
- // Respond back with success.
- informInputBridgeConnected(token);
-
} catch (IOException ioe) {
Slog.e(TAG, "Cannot create device for " + name);
+ return false;
}
+ return true;
}
private void closeInputBridgeInternalLocked(IBinder token) {
@@ -149,7 +133,6 @@
}
mBridgeMap.remove(token);
- mProviderMap.remove(token);
}
private void clearInputBridgeInternalLocked(IBinder token) {
@@ -220,47 +203,7 @@
}
}
- private final class UserHandler extends Handler {
-
- public static final int MSG_START = 1;
- public static final int MSG_INPUT_BRIDGE_CONNECTED = 2;
-
- private final TvRemoteProviderWatcher mWatcher;
- private boolean mRunning;
-
- public UserHandler(UserProvider provider, Context context) {
- super(Looper.getMainLooper(), null, true);
- mWatcher = new TvRemoteProviderWatcher(context, provider, this);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_START: {
- start();
- break;
- }
- case MSG_INPUT_BRIDGE_CONNECTED: {
- IBinder token = (IBinder) msg.obj;
- TvRemoteProviderProxy provider = mProviderMap.get(token);
- if (provider != null) {
- provider.inputBridgeConnected(token);
- }
- break;
- }
- }
- }
-
- private void start() {
- if (!mRunning) {
- mRunning = true;
- mWatcher.start(); // also starts all providers
- }
- }
- }
-
- private final class UserProvider implements TvRemoteProviderWatcher.ProviderMethods,
- TvRemoteProviderProxy.ProviderMethods {
+ private final class UserProvider implements TvRemoteProviderProxy.ProviderMethods {
private final TvRemoteService mService;
@@ -269,8 +212,8 @@
}
@Override
- public void openInputBridge(TvRemoteProviderProxy provider, IBinder token, String name,
- int width, int height, int maxPointers) {
+ public boolean openInputBridge(TvRemoteProviderProxy provider, IBinder token, String name,
+ int width, int height, int maxPointers) {
if (DEBUG) {
Slog.d(TAG, "openInputBridge(), token: " + token +
", name: " + name + ", width: " + width +
@@ -278,10 +221,8 @@
}
synchronized (mLock) {
- if (mProviderList.contains(provider)) {
- mService.openInputBridgeInternalLocked(provider, token, name, width, height,
- maxPointers);
- }
+ return mService.openInputBridgeInternalLocked(token, name, width,
+ height, maxPointers);
}
}
@@ -289,9 +230,7 @@
public void closeInputBridge(TvRemoteProviderProxy provider, IBinder token) {
if (DEBUG) Slog.d(TAG, "closeInputBridge(), token: " + token);
synchronized (mLock) {
- if (mProviderList.contains(provider)) {
mService.closeInputBridgeInternalLocked(token);
- }
}
}
@@ -299,9 +238,7 @@
public void clearInputBridge(TvRemoteProviderProxy provider, IBinder token) {
if (DEBUG) Slog.d(TAG, "clearInputBridge(), token: " + token);
synchronized (mLock) {
- if (mProviderList.contains(provider)) {
mService.clearInputBridgeInternalLocked(token);
- }
}
}
@@ -311,9 +248,7 @@
Slog.d(TAG, "sendKeyDown(), token: " + token + ", keyCode: " + keyCode);
}
synchronized (mLock) {
- if (mProviderList.contains(provider)) {
mService.sendKeyDownInternalLocked(token, keyCode);
- }
}
}
@@ -323,9 +258,7 @@
Slog.d(TAG, "sendKeyUp(), token: " + token + ", keyCode: " + keyCode);
}
synchronized (mLock) {
- if (mProviderList.contains(provider)) {
mService.sendKeyUpInternalLocked(token, keyCode);
- }
}
}
@@ -336,9 +269,7 @@
Slog.d(TAG, "sendPointerDown(), token: " + token + ", pointerId: " + pointerId);
}
synchronized (mLock) {
- if (mProviderList.contains(provider)) {
mService.sendPointerDownInternalLocked(token, pointerId, x, y);
- }
}
}
@@ -348,9 +279,7 @@
Slog.d(TAG, "sendPointerUp(), token: " + token + ", pointerId: " + pointerId);
}
synchronized (mLock) {
- if (mProviderList.contains(provider)) {
mService.sendPointerUpInternalLocked(token, pointerId);
- }
}
}
@@ -358,29 +287,7 @@
public void sendPointerSync(TvRemoteProviderProxy provider, IBinder token) {
if (DEBUG_KEYS) Slog.d(TAG, "sendPointerSync(), token: " + token);
synchronized (mLock) {
- if (mProviderList.contains(provider)) {
mService.sendPointerSyncInternalLocked(token);
- }
- }
- }
-
- @Override
- public void addProvider(TvRemoteProviderProxy provider) {
- if (DEBUG) Slog.d(TAG, "addProvider " + provider);
- synchronized (mLock) {
- provider.setProviderSink(this);
- mProviderList.add(provider);
- Slog.d(TAG, "provider: " + provider.toString());
- }
- }
-
- @Override
- public void removeProvider(TvRemoteProviderProxy provider) {
- if (DEBUG) Slog.d(TAG, "removeProvider " + provider);
- synchronized (mLock) {
- if (mProviderList.remove(provider) == false) {
- Slog.e(TAG, "Unknown provider " + provider);
- }
}
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 8bb37bb..7ee210e 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -839,11 +839,12 @@
// so that the divider matches and remove this logic.
// TODO: This is currently only called when entering split-screen while in another
// task, and from the tests
- // TODO (b/78247419): Check if launcher and overview are same then move home stack
- // instead of recents stack. Then fix the rotation animation from fullscreen to
- // minimized mode
+ // TODO (b/78247419): Fix the rotation animation from fullscreen to minimized mode
+ final boolean isRecentsComponentHome =
+ mService.getRecentTasks().isRecentsComponentHomeActivity(mCurrentUser);
final ActivityStack recentStack = display.getOrCreateStack(
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_RECENTS,
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
+ isRecentsComponentHome ? ACTIVITY_TYPE_HOME : ACTIVITY_TYPE_RECENTS,
true /* onTop */);
recentStack.moveToFront("setWindowingMode");
// If task moved to docked stack - show recents if needed.
@@ -4940,6 +4941,12 @@
}
}
+
+ Rect getDefaultPictureInPictureBounds(float aspectRatio) {
+ if (getTaskStack() == null) return null;
+ return getTaskStack().getPictureInPictureBounds(aspectRatio, null /* currentStackBounds */);
+ }
+
void animateResizePinnedStack(Rect sourceHintBounds, Rect toBounds, int animationDuration,
boolean fromFullscreen) {
if (!inPinnedWindowingMode()) return;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 4cc9f4c..47be792 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1550,10 +1550,11 @@
mService.mUgmInternal.grantUriPermissionFromIntent(mCallingUid, mStartActivity.packageName,
mIntent, mStartActivity.getUriPermissionsLocked(), mStartActivity.mUserId);
- mService.getPackageManagerInternalLocked().grantEphemeralAccess(
+ mService.getPackageManagerInternalLocked().grantImplicitAccess(
mStartActivity.mUserId, mIntent,
- UserHandle.getAppId(mStartActivity.info.applicationInfo.uid),
- UserHandle.getAppId(mCallingUid));
+ UserHandle.getAppId(mCallingUid),
+ UserHandle.getAppId(mStartActivity.info.applicationInfo.uid)
+ );
if (newTask) {
EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, mStartActivity.mUserId,
mStartActivity.getTaskRecord().taskId);
@@ -2341,7 +2342,12 @@
REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
"reparentingHome");
mMovedToFront = true;
+ } else if (launchStack.topTask() == null) {
+ // The task does not need to be reparented to the launch stack. Remove the
+ // launch stack if there is no activity in it.
+ launchStack.remove();
}
+
mOptions = null;
// We are moving a task to the front, use starting window to hide initial drawn
@@ -2542,7 +2548,8 @@
final boolean onTop =
(aOptions == null || !aOptions.getAvoidMoveToFront()) && !mLaunchTaskBehind;
final ActivityStack stack =
- mRootActivityContainer.getLaunchStack(r, aOptions, task, onTop, mLaunchParams);
+ mRootActivityContainer.getLaunchStack(r, aOptions, task, onTop, mLaunchParams,
+ mRequest.realCallingPid, mRequest.realCallingUid);
return stack;
}
// Otherwise handle adjacent launch.
@@ -2660,11 +2667,24 @@
return this;
}
+ /**
+ * Sets the pid of the caller who originally started the activity.
+ *
+ * Normally, the pid/uid would be the calling pid from the binder call.
+ * However, in case of a {@link PendingIntent}, the pid/uid pair of the caller is considered
+ * the original entity that created the pending intent, in contrast to setRealCallingPid/Uid,
+ * which represents the entity who invoked pending intent via {@link PendingIntent#send}.
+ */
ActivityStarter setCallingPid(int pid) {
mRequest.callingPid = pid;
return this;
}
+ /**
+ * Sets the uid of the caller who originally started the activity.
+ *
+ * @see #setCallingPid
+ */
ActivityStarter setCallingUid(int uid) {
mRequest.callingUid = uid;
return this;
@@ -2675,11 +2695,25 @@
return this;
}
+ /**
+ * Sets the pid of the caller who requested to launch the activity.
+ *
+ * The pid/uid represents the caller who launches the activity in this request.
+ * It will almost same as setCallingPid/Uid except when processing {@link PendingIntent}:
+ * the pid/uid will be the caller who called {@link PendingIntent#send()}.
+ *
+ * @see #setCallingPid
+ */
ActivityStarter setRealCallingPid(int pid) {
mRequest.realCallingPid = pid;
return this;
}
+ /**
+ * Sets the uid of the caller who requested to launch the activity.
+ *
+ * @see #setRealCallingPid
+ */
ActivityStarter setRealCallingUid(int uid) {
mRequest.realCallingUid = uid;
return this;
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 71f02e8..93b8f00 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -1021,7 +1021,7 @@
if (DEBUG_ADD_REMOVE) Slog.v(TAG, "notifyAppStopped: " + this);
mAppStopped = true;
// Reset the last saved PiP snap fraction on app stop.
- mDisplayContent.mPinnedStackControllerLocked.resetReentrySnapFraction(mActivityComponent);
+ mDisplayContent.mPinnedStackControllerLocked.resetReentrySnapFraction(this);
destroySurfaces();
// Remove any starting window that was added for this app if they are still around.
removeStartingWindow();
@@ -1705,7 +1705,10 @@
return;
}
- if (prevWinMode == WINDOWING_MODE_PINNED && winMode != WINDOWING_MODE_UNDEFINED
+ if (prevWinMode != WINDOWING_MODE_UNDEFINED && winMode == WINDOWING_MODE_PINNED) {
+ // Entering PiP from fullscreen, reset the snap fraction
+ mDisplayContent.mPinnedStackControllerLocked.resetReentrySnapFraction(this);
+ } else if (prevWinMode == WINDOWING_MODE_PINNED && winMode != WINDOWING_MODE_UNDEFINED
&& !isHidden()) {
// Leaving PiP to fullscreen, save the snap fraction based on the pre-animation bounds
// for the next re-entry into PiP (assuming the activity is not hidden or destroyed)
@@ -1723,8 +1726,8 @@
stackBounds = mTmpRect;
pinnedStack.getBounds(stackBounds);
}
- mDisplayContent.mPinnedStackControllerLocked.saveReentrySnapFraction(
- mActivityComponent, stackBounds);
+ mDisplayContent.mPinnedStackControllerLocked.saveReentrySnapFraction(this,
+ stackBounds);
}
} else if (shouldStartChangeTransition(prevWinMode, winMode)) {
initializeChangeTransition(mTmpPrevBounds);
@@ -2500,14 +2503,18 @@
@Override
public SurfaceControl getAnimationLeashParent() {
- // All normal app transitions take place in an animation layer which is below the pinned
- // stack but may be above the parent stacks of the given animating apps.
// For transitions in the pinned stack (menu activity) we just let them occur as a child
// of the pinned stack.
- if (!inPinnedWindowingMode()) {
- return getAppAnimationLayer();
- } else {
+ // All normal app transitions take place in an animation layer which is below the pinned
+ // stack but may be above the parent stacks of the given animating apps by default. When
+ // a new hierarchical animation is enabled, we just let them occur as a child of the parent
+ // stack, i.e. the hierarchy of the surfaces is unchanged.
+ if (inPinnedWindowingMode()) {
return getStack().getSurfaceControl();
+ } else if (WindowManagerService.sHierarchicalAnimations) {
+ return super.getAnimationLeashParent();
+ } else {
+ return getAppAnimationLayer();
}
}
diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java
index 8e57fec..ef0049b 100644
--- a/services/core/java/com/android/server/wm/PinnedStackController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackController.java
@@ -27,7 +27,6 @@
import android.annotation.NonNull;
import android.app.RemoteAction;
-import android.content.ComponentName;
import android.content.pm.ParceledListSlice;
import android.content.res.Resources;
import android.graphics.Point;
@@ -51,6 +50,7 @@
import com.android.server.UiThread;
import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
@@ -74,7 +74,7 @@
private static final String TAG = TAG_WITH_CLASS_NAME ? "PinnedStackController" : TAG_WM;
- private static final float INVALID_SNAP_FRACTION = -1f;
+ public static final float INVALID_SNAP_FRACTION = -1f;
private final WindowManagerService mService;
private final DisplayContent mDisplayContent;
private final Handler mHandler = UiThread.getHandler();
@@ -106,6 +106,9 @@
private int mDefaultStackGravity;
private float mDefaultAspectRatio;
private Point mScreenEdgeInsets;
+ private int mCurrentMinSize;
+ private float mReentrySnapFraction = INVALID_SNAP_FRACTION;
+ private WeakReference<AppWindowToken> mLastPipActivity = null;
// The aspect ratio bounds of the PIP.
private float mMinAspectRatio;
@@ -115,6 +118,7 @@
private final DisplayMetrics mTmpMetrics = new DisplayMetrics();
private final Rect mTmpInsets = new Rect();
private final Rect mTmpRect = new Rect();
+ private final Rect mTmpAnimatingBoundsRect = new Rect();
private final Point mTmpDisplaySize = new Point();
@@ -132,19 +136,16 @@
}
@Override
- public int getDisplayRotation() {
- synchronized (mService.mGlobalLock) {
- return mDisplayInfo.rotation;
- }
+ public void setMinEdgeSize(int minEdgeSize) {
+ mHandler.post(() -> {
+ mCurrentMinSize = Math.max(mDefaultMinSize, minEdgeSize);
+ });
}
@Override
- public void startAnimation(Rect destinationBounds, Rect sourceRectHint,
- int animationDuration) {
+ public int getDisplayRotation() {
synchronized (mService.mGlobalLock) {
- final TaskStack pinnedStack = mDisplayContent.getPinnedStack();
- pinnedStack.animateResizePinnedStack(destinationBounds,
- sourceRectHint, animationDuration, true /* fromFullscreen */);
+ return mDisplayInfo.rotation;
}
}
}
@@ -187,6 +188,7 @@
final Resources res = mService.mContext.getResources();
mDefaultMinSize = res.getDimensionPixelSize(
com.android.internal.R.dimen.default_minimal_size_pip_resizable_task);
+ mCurrentMinSize = mDefaultMinSize;
mDefaultAspectRatio = res.getFloat(
com.android.internal.R.dimen.config_pictureInPictureDefaultAspectRatio);
final String screenEdgeInsetsDpString = res.getString(
@@ -214,7 +216,6 @@
listener.asBinder().linkToDeath(mPinnedStackListenerDeathHandler, 0);
listener.onListenerRegistered(mCallbacks);
mPinnedStackListener = listener;
- notifyDisplayInfoChanged(mDisplayInfo);
notifyImeVisibilityChanged(mIsImeShowing, mImeHeight);
notifyShelfVisibilityChanged(mIsShelfShowing, mShelfHeight);
// The movement bounds notification needs to be sent before the minimized state, since
@@ -237,34 +238,58 @@
}
/**
+ * Returns the current bounds (or the default bounds if there are no current bounds) with the
+ * specified aspect ratio.
+ */
+ Rect transformBoundsToAspectRatio(Rect stackBounds, float aspectRatio,
+ boolean useCurrentMinEdgeSize) {
+ // Save the snap fraction, calculate the aspect ratio based on screen size
+ final float snapFraction = mSnapAlgorithm.getSnapFraction(stackBounds,
+ getMovementBounds(stackBounds));
+
+ final int minEdgeSize = useCurrentMinEdgeSize ? mCurrentMinSize : mDefaultMinSize;
+ final Size size = mSnapAlgorithm.getSizeForAspectRatio(aspectRatio, minEdgeSize,
+ mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
+ final int left = (int) (stackBounds.centerX() - size.getWidth() / 2f);
+ final int top = (int) (stackBounds.centerY() - size.getHeight() / 2f);
+ stackBounds.set(left, top, left + size.getWidth(), top + size.getHeight());
+ mSnapAlgorithm.applySnapFraction(stackBounds, getMovementBounds(stackBounds), snapFraction);
+ if (mIsMinimized) {
+ applyMinimizedOffset(stackBounds, getMovementBounds(stackBounds));
+ }
+ return stackBounds;
+ }
+
+ /**
* Saves the current snap fraction for re-entry of the current activity into PiP.
*/
- void saveReentrySnapFraction(final ComponentName componentName, final Rect stackBounds) {
- if (mPinnedStackListener == null) return;
- try {
- mPinnedStackListener.onSaveReentrySnapFraction(componentName, stackBounds);
- } catch (RemoteException e) {
- Slog.e(TAG_WM, "Error delivering save reentry fraction event.", e);
- }
+ void saveReentrySnapFraction(final AppWindowToken token, final Rect stackBounds) {
+ mReentrySnapFraction = getSnapFraction(stackBounds);
+ mLastPipActivity = new WeakReference<>(token);
}
/**
* Resets the last saved snap fraction so that the default bounds will be returned.
*/
- void resetReentrySnapFraction(ComponentName componentName) {
- if (mPinnedStackListener == null) return;
- try {
- mPinnedStackListener.onResetReentrySnapFraction(componentName);
- } catch (RemoteException e) {
- Slog.e(TAG_WM, "Error delivering reset reentry fraction event.", e);
+ void resetReentrySnapFraction(AppWindowToken token) {
+ if (mLastPipActivity != null && mLastPipActivity.get() == token) {
+ mReentrySnapFraction = INVALID_SNAP_FRACTION;
+ mLastPipActivity = null;
}
}
/**
+ * @return the default bounds to show the PIP when there is no active PIP.
+ */
+ Rect getDefaultOrLastSavedBounds() {
+ return getDefaultBounds(mReentrySnapFraction);
+ }
+
+ /**
* @return the default bounds to show the PIP, if a {@param snapFraction} is provided, then it
* will apply the default bounds to the provided snap fraction.
*/
- private Rect getDefaultBounds(float snapFraction) {
+ Rect getDefaultBounds(float snapFraction) {
synchronized (mService.mGlobalLock) {
final Rect insetBounds = new Rect();
getInsetBounds(insetBounds);
@@ -286,18 +311,13 @@
}
}
- private void setDisplayInfo(DisplayInfo displayInfo) {
- mDisplayInfo.copyFrom(displayInfo);
- notifyDisplayInfoChanged(mDisplayInfo);
- }
-
/**
* In the case where the display rotation is changed but there is no stack, we can't depend on
* onTaskStackBoundsChanged() to be called. But we still should update our known display info
* with the new state so that we can update SystemUI.
*/
synchronized void onDisplayInfoChanged(DisplayInfo displayInfo) {
- setDisplayInfo(displayInfo);
+ mDisplayInfo.copyFrom(displayInfo);
notifyMovementBoundsChanged(false /* fromImeAdjustment */, false /* fromShelfAdjustment */);
}
@@ -315,7 +335,7 @@
} else if (targetBounds.isEmpty()) {
// The stack is null, we are just initializing the stack, so just store the display
// info and ignore
- setDisplayInfo(displayInfo);
+ mDisplayInfo.copyFrom(displayInfo);
outBounds.setEmpty();
return false;
}
@@ -325,8 +345,7 @@
// Calculate the snap fraction of the current stack along the old movement bounds
final float snapFraction = getSnapFraction(postChangeStackBounds);
-
- setDisplayInfo(displayInfo);
+ mDisplayInfo.copyFrom(displayInfo);
// Calculate the stack bounds in the new orientation to the same same fraction along the
// rotated movement bounds.
@@ -387,11 +406,8 @@
void setAspectRatio(float aspectRatio) {
if (Float.compare(mAspectRatio, aspectRatio) != 0) {
mAspectRatio = aspectRatio;
- notifyAspectRatioChanged(aspectRatio);
notifyMovementBoundsChanged(false /* fromImeAdjustment */,
false /* fromShelfAdjustment */);
- notifyPrepareAnimation(null /* sourceHintRect */, aspectRatio,
- null /* stackBounds */);
}
}
@@ -413,10 +429,6 @@
notifyActionsChanged(mActions);
}
- void prepareAnimation(Rect sourceRectHint, float aspectRatio, Rect stackBounds) {
- notifyPrepareAnimation(sourceRectHint, aspectRatio, stackBounds);
- }
-
private boolean isSameDimensionAndRotation(@NonNull DisplayInfo display1,
@NonNull DisplayInfo display2) {
Preconditions.checkNotNull(display1);
@@ -449,15 +461,6 @@
}
}
- private void notifyAspectRatioChanged(float aspectRatio) {
- if (mPinnedStackListener == null) return;
- try {
- mPinnedStackListener.onAspectRatioChanged(aspectRatio);
- } catch (RemoteException e) {
- Slog.e(TAG_WM, "Error delivering aspect ratio changed event.", e);
- }
- }
-
/**
* Notifies listeners that the PIP minimized state has changed.
*/
@@ -494,13 +497,23 @@
return;
}
try {
- final Rect animatingBounds = new Rect();
+ final Rect insetBounds = new Rect();
+ getInsetBounds(insetBounds);
+ final Rect normalBounds = getDefaultBounds(INVALID_SNAP_FRACTION);
+ if (isValidPictureInPictureAspectRatio(mAspectRatio)) {
+ transformBoundsToAspectRatio(normalBounds, mAspectRatio,
+ false /* useCurrentMinEdgeSize */);
+ }
+ final Rect animatingBounds = mTmpAnimatingBoundsRect;
final TaskStack pinnedStack = mDisplayContent.getPinnedStack();
if (pinnedStack != null) {
pinnedStack.getAnimationOrCurrentBounds(animatingBounds);
+ } else {
+ animatingBounds.set(normalBounds);
}
- mPinnedStackListener.onMovementBoundsChanged(animatingBounds,
- fromImeAdjustment, fromShelfAdjustment);
+ mPinnedStackListener.onMovementBoundsChanged(insetBounds, normalBounds,
+ animatingBounds, fromImeAdjustment, fromShelfAdjustment,
+ mDisplayInfo.rotation);
} catch (RemoteException e) {
Slog.e(TAG_WM, "Error delivering actions changed event.", e);
}
@@ -508,30 +521,6 @@
}
/**
- * Notifies listeners that the PIP animation is about to happen.
- */
- private void notifyDisplayInfoChanged(DisplayInfo displayInfo) {
- if (mPinnedStackListener == null) return;
- try {
- mPinnedStackListener.onDisplayInfoChanged(displayInfo);
- } catch (RemoteException e) {
- Slog.e(TAG_WM, "Error delivering DisplayInfo changed event.", e);
- }
- }
-
- /**
- * Notifies listeners that the PIP animation is about to happen.
- */
- private void notifyPrepareAnimation(Rect sourceRectHint, float aspectRatio, Rect stackBounds) {
- if (mPinnedStackListener == null) return;
- try {
- mPinnedStackListener.onPrepareAnimation(sourceRectHint, aspectRatio, stackBounds);
- } catch (RemoteException e) {
- Slog.e(TAG_WM, "Error delivering prepare animation event.", e);
- }
- }
-
- /**
* @return the bounds on the screen that the PIP can be visible in.
*/
private void getInsetBounds(Rect outRect) {
@@ -615,6 +604,7 @@
pw.println(prefix + " mImeHeight=" + mImeHeight);
pw.println(prefix + " mIsShelfShowing=" + mIsShelfShowing);
pw.println(prefix + " mShelfHeight=" + mShelfHeight);
+ pw.println(prefix + " mReentrySnapFraction=" + mReentrySnapFraction);
pw.println(prefix + " mIsMinimized=" + mIsMinimized);
pw.println(prefix + " mAspectRatio=" + mAspectRatio);
pw.println(prefix + " mMinAspectRatio=" + mMinAspectRatio);
diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java
index d9e30a2..6d46716 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -193,6 +193,9 @@
/** Set when a power hint has started, but not ended. */
private boolean mPowerHintSent;
+ /** Used to keep ensureActivitiesVisible() from being entered recursively. */
+ private boolean mInEnsureActivitiesVisible = false;
+
// The default minimal size that will be used if the activity doesn't specify its minimal size.
// It will be calculated when the default display gets added.
int mDefaultMinSizeOfResizeableTaskDp = -1;
@@ -805,8 +808,14 @@
*/
void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
boolean preserveWindows, boolean notifyClients) {
- mStackSupervisor.getKeyguardController().beginActivityVisibilityUpdate();
+ if (mInEnsureActivitiesVisible) {
+ // Don't do recursive work.
+ return;
+ }
+ mInEnsureActivitiesVisible = true;
+
try {
+ mStackSupervisor.getKeyguardController().beginActivityVisibilityUpdate();
// First the front stacks. In case any are not fullscreen and are in front of home.
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
final ActivityDisplay display = mActivityDisplays.get(displayNdx);
@@ -815,6 +824,7 @@
}
} finally {
mStackSupervisor.getKeyguardController().endActivityVisibilityUpdate();
+ mInEnsureActivitiesVisible = false;
}
}
@@ -959,6 +969,10 @@
// Need to make sure the pinned stack exist so we can resize it below...
stack = display.getOrCreateStack(WINDOWING_MODE_PINNED, r.getActivityType(), ON_TOP);
+ // Calculate the target bounds here before the task is reparented back into pinned windowing
+ // mode (which will reset the saved bounds)
+ final Rect destBounds = stack.getDefaultPictureInPictureBounds(aspectRatio);
+
try {
final TaskRecord task = r.getTaskRecord();
// Resize the pinned stack to match the current size of the task the activity we are
@@ -997,14 +1011,9 @@
mService.continueWindowLayout();
}
- // Notify the pinned stack controller to prepare the PiP animation, expect callback
- // delivered from SystemUI to WM to start the animation.
- final PinnedStackController pinnedStackController =
- display.mDisplayContent.getPinnedStackController();
- pinnedStackController.prepareAnimation(sourceHintBounds, aspectRatio,
- null /* stackBounds */);
+ stack.animateResizePinnedStack(sourceHintBounds, destBounds, -1 /* animationDuration */,
+ true /* fromFullscreen */);
- // TODO: revisit the following statement after the animation is moved from WM to SysUI.
// Update the visibility of all activities after the they have been reparented to the new
// stack. This MUST run after the animation above is scheduled to ensure that the windows
// drawn signal is scheduled after the bounds animation start call on the bounds animator
@@ -1615,7 +1624,8 @@
<T extends ActivityStack> T getLaunchStack(@Nullable ActivityRecord r,
@Nullable ActivityOptions options, @Nullable TaskRecord candidateTask, boolean onTop) {
- return getLaunchStack(r, options, candidateTask, onTop, null /* launchParams */);
+ return getLaunchStack(r, options, candidateTask, onTop, null /* launchParams */,
+ -1 /* no realCallingPid */, -1 /* no realCallingUid */);
}
/**
@@ -1624,13 +1634,16 @@
* @param r The activity we are trying to launch. Can be null.
* @param options The activity options used to the launch. Can be null.
* @param candidateTask The possible task the activity might be launched in. Can be null.
- * @params launchParams The resolved launch params to use.
+ * @param launchParams The resolved launch params to use.
+ * @param realCallingPid The pid from {@link ActivityStarter#setRealCallingPid}
+ * @param realCallingUid The uid from {@link ActivityStarter#setRealCallingUid}
*
* @return The stack to use for the launch or INVALID_STACK_ID.
*/
<T extends ActivityStack> T getLaunchStack(@Nullable ActivityRecord r,
@Nullable ActivityOptions options, @Nullable TaskRecord candidateTask, boolean onTop,
- @Nullable LaunchParamsController.LaunchParams launchParams) {
+ @Nullable LaunchParamsController.LaunchParams launchParams, int realCallingPid,
+ int realCallingUid) {
int taskId = INVALID_TASK_ID;
int displayId = INVALID_DISPLAY;
//Rect bounds = null;
@@ -1661,7 +1674,15 @@
if (launchParams != null && launchParams.mPreferredDisplayId != INVALID_DISPLAY) {
displayId = launchParams.mPreferredDisplayId;
}
- if (displayId != INVALID_DISPLAY && canLaunchOnDisplay(r, displayId)) {
+ final boolean canLaunchOnDisplayFromStartRequest =
+ realCallingPid != 0 && realCallingUid > 0 && r != null
+ && mStackSupervisor.canPlaceEntityOnDisplay(displayId, realCallingPid,
+ realCallingUid, r.info);
+ // Checking if the activity's launch caller, or the realCallerId of the activity from
+ // start request (i.e. entity that invokes PendingIntent) is allowed to launch on the
+ // display.
+ if (displayId != INVALID_DISPLAY && (canLaunchOnDisplay(r, displayId)
+ || canLaunchOnDisplayFromStartRequest)) {
if (r != null) {
stack = (T) getValidLaunchStackOnDisplay(displayId, r, candidateTask, options,
launchParams);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 3a2eb57..762066c 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -613,6 +613,9 @@
@Override
public SurfaceControl getAnimationLeashParent() {
+ if (!WindowManagerService.sHierarchicalAnimations) {
+ return super.getAnimationLeashParent();
+ }
// Currently, only the recents animation will create animation leashes for tasks. In this
// case, reparent the task to the home animation layer while it is being animated to allow
// the home activity to reorder the app windows relative to its own.
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 58542e5..239bd00 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -1661,6 +1661,40 @@
}
/**
+ * @return the current stack bounds transformed to the given {@param aspectRatio}. If
+ * the default bounds is {@code null}, then the {@param aspectRatio} is applied to the
+ * default bounds.
+ */
+ Rect getPictureInPictureBounds(float aspectRatio, Rect stackBounds) {
+ if (!mWmService.mAtmService.mSupportsPictureInPicture) {
+ return null;
+ }
+
+ final DisplayContent displayContent = getDisplayContent();
+ if (displayContent == null) {
+ return null;
+ }
+
+ if (!inPinnedWindowingMode()) {
+ return null;
+ }
+
+ final PinnedStackController pinnedStackController =
+ displayContent.getPinnedStackController();
+ if (stackBounds == null) {
+ // Calculate the aspect ratio bounds from the default bounds
+ stackBounds = pinnedStackController.getDefaultOrLastSavedBounds();
+ }
+
+ if (pinnedStackController.isValidPictureInPictureAspectRatio(aspectRatio)) {
+ return pinnedStackController.transformBoundsToAspectRatio(stackBounds, aspectRatio,
+ true /* useCurrentMinEdgeSize */);
+ } else {
+ return stackBounds;
+ }
+ }
+
+ /**
* Animates the pinned stack.
*/
void animateResizePinnedStack(Rect toBounds, Rect sourceHintBounds,
@@ -1737,11 +1771,6 @@
return;
}
- final DisplayContent displayContent = getDisplayContent();
- if (displayContent == null) {
- return;
- }
-
if (!inPinnedWindowingMode()) {
return;
}
@@ -1752,10 +1781,13 @@
if (Float.compare(aspectRatio, pinnedStackController.getAspectRatio()) == 0) {
return;
}
-
- // Notify the pinned stack controller about aspect ratio change.
- // This would result a callback delivered from SystemUI to WM to start animation,
- // if the bounds are ought to be altered due to aspect ratio change.
+ getAnimationOrCurrentBounds(mTmpFromBounds);
+ mTmpToBounds.set(mTmpFromBounds);
+ getPictureInPictureBounds(aspectRatio, mTmpToBounds);
+ if (!mTmpToBounds.equals(mTmpFromBounds)) {
+ animateResizePinnedStack(mTmpToBounds, null /* sourceHintBounds */,
+ -1 /* duration */, false /* fromFullscreen */);
+ }
pinnedStackController.setAspectRatio(
pinnedStackController.isValidPictureInPictureAspectRatio(aspectRatio)
? aspectRatio : -1f);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 607a013..1ebaa5d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -375,6 +375,22 @@
// trying to apply a new one.
private static final boolean ALWAYS_KEEP_CURRENT = true;
+ /**
+ * If set, new app transition framework which supports setting animation on any element
+ * in a surface is used.
+ * <p>
+ * Only set this to non-zero once the new app transition framework is productionalized.
+ * </p>
+ */
+ private static final String HIERARCHICAL_ANIMATIONS_PROPERTY =
+ "persist.wm.hierarchical_animations";
+
+ /**
+ * @see #HIERARCHICAL_ANIMATIONS_PROPERTY
+ */
+ static boolean sHierarchicalAnimations =
+ SystemProperties.getBoolean(HIERARCHICAL_ANIMATIONS_PROPERTY, false);
+
// Enums for animation scale update types.
@Retention(RetentionPolicy.SOURCE)
@IntDef({WINDOW_ANIMATION_SCALE, TRANSITION_ANIMATION_SCALE, ANIMATION_DURATION_SCALE})
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 478bc88..704c808 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1113,142 +1113,107 @@
info.writePoliciesToXml(out);
out.endTag(null, TAG_POLICIES);
if (minimumPasswordMetrics.quality != PASSWORD_QUALITY_UNSPECIFIED) {
- out.startTag(null, TAG_PASSWORD_QUALITY);
- out.attribute(null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.quality));
- out.endTag(null, TAG_PASSWORD_QUALITY);
+ writeAttributeValueToXml(
+ out, TAG_PASSWORD_QUALITY, minimumPasswordMetrics.quality);
if (minimumPasswordMetrics.length != DEF_MINIMUM_PASSWORD_LENGTH) {
- out.startTag(null, TAG_MIN_PASSWORD_LENGTH);
- out.attribute(
- null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.length));
- out.endTag(null, TAG_MIN_PASSWORD_LENGTH);
- }
- if(passwordHistoryLength != DEF_PASSWORD_HISTORY_LENGTH) {
- out.startTag(null, TAG_PASSWORD_HISTORY_LENGTH);
- out.attribute(null, ATTR_VALUE, Integer.toString(passwordHistoryLength));
- out.endTag(null, TAG_PASSWORD_HISTORY_LENGTH);
+ writeAttributeValueToXml(
+ out, TAG_MIN_PASSWORD_LENGTH, minimumPasswordMetrics.length);
}
if (minimumPasswordMetrics.upperCase != DEF_MINIMUM_PASSWORD_UPPER_CASE) {
- out.startTag(null, TAG_MIN_PASSWORD_UPPERCASE);
- out.attribute(
- null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.upperCase));
- out.endTag(null, TAG_MIN_PASSWORD_UPPERCASE);
+ writeAttributeValueToXml(
+ out, TAG_MIN_PASSWORD_UPPERCASE, minimumPasswordMetrics.upperCase);
}
if (minimumPasswordMetrics.lowerCase != DEF_MINIMUM_PASSWORD_LOWER_CASE) {
- out.startTag(null, TAG_MIN_PASSWORD_LOWERCASE);
- out.attribute(
- null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.lowerCase));
- out.endTag(null, TAG_MIN_PASSWORD_LOWERCASE);
+ writeAttributeValueToXml(
+ out, TAG_MIN_PASSWORD_LOWERCASE, minimumPasswordMetrics.lowerCase);
}
if (minimumPasswordMetrics.letters != DEF_MINIMUM_PASSWORD_LETTERS) {
- out.startTag(null, TAG_MIN_PASSWORD_LETTERS);
- out.attribute(
- null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.letters));
- out.endTag(null, TAG_MIN_PASSWORD_LETTERS);
+ writeAttributeValueToXml(
+ out, TAG_MIN_PASSWORD_LETTERS, minimumPasswordMetrics.letters);
}
if (minimumPasswordMetrics.numeric != DEF_MINIMUM_PASSWORD_NUMERIC) {
- out.startTag(null, TAG_MIN_PASSWORD_NUMERIC);
- out.attribute(
- null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.numeric));
- out.endTag(null, TAG_MIN_PASSWORD_NUMERIC);
+ writeAttributeValueToXml(
+ out, TAG_MIN_PASSWORD_NUMERIC, minimumPasswordMetrics.numeric);
}
if (minimumPasswordMetrics.symbols != DEF_MINIMUM_PASSWORD_SYMBOLS) {
- out.startTag(null, TAG_MIN_PASSWORD_SYMBOLS);
- out.attribute(
- null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.symbols));
- out.endTag(null, TAG_MIN_PASSWORD_SYMBOLS);
+ writeAttributeValueToXml(
+ out, TAG_MIN_PASSWORD_SYMBOLS, minimumPasswordMetrics.symbols);
}
if (minimumPasswordMetrics.nonLetter > DEF_MINIMUM_PASSWORD_NON_LETTER) {
- out.startTag(null, TAG_MIN_PASSWORD_NONLETTER);
- out.attribute(
- null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.nonLetter));
- out.endTag(null, TAG_MIN_PASSWORD_NONLETTER);
+ writeAttributeValueToXml(
+ out, TAG_MIN_PASSWORD_NONLETTER, minimumPasswordMetrics.nonLetter);
}
}
+ if (passwordHistoryLength != DEF_PASSWORD_HISTORY_LENGTH) {
+ writeAttributeValueToXml(
+ out, TAG_PASSWORD_HISTORY_LENGTH, passwordHistoryLength);
+ }
if (maximumTimeToUnlock != DEF_MAXIMUM_TIME_TO_UNLOCK) {
- out.startTag(null, TAG_MAX_TIME_TO_UNLOCK);
- out.attribute(null, ATTR_VALUE, Long.toString(maximumTimeToUnlock));
- out.endTag(null, TAG_MAX_TIME_TO_UNLOCK);
+ writeAttributeValueToXml(
+ out, TAG_MAX_TIME_TO_UNLOCK, maximumTimeToUnlock);
}
if (strongAuthUnlockTimeout != DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS) {
- out.startTag(null, TAG_STRONG_AUTH_UNLOCK_TIMEOUT);
- out.attribute(null, ATTR_VALUE, Long.toString(strongAuthUnlockTimeout));
- out.endTag(null, TAG_STRONG_AUTH_UNLOCK_TIMEOUT);
+ writeAttributeValueToXml(
+ out, TAG_STRONG_AUTH_UNLOCK_TIMEOUT, strongAuthUnlockTimeout);
}
if (maximumFailedPasswordsForWipe != DEF_MAXIMUM_FAILED_PASSWORDS_FOR_WIPE) {
- out.startTag(null, TAG_MAX_FAILED_PASSWORD_WIPE);
- out.attribute(null, ATTR_VALUE, Integer.toString(maximumFailedPasswordsForWipe));
- out.endTag(null, TAG_MAX_FAILED_PASSWORD_WIPE);
+ writeAttributeValueToXml(
+ out, TAG_MAX_FAILED_PASSWORD_WIPE, maximumFailedPasswordsForWipe);
}
if (specifiesGlobalProxy) {
- out.startTag(null, TAG_SPECIFIES_GLOBAL_PROXY);
- out.attribute(null, ATTR_VALUE, Boolean.toString(specifiesGlobalProxy));
- out.endTag(null, TAG_SPECIFIES_GLOBAL_PROXY);
+ writeAttributeValueToXml(
+ out, TAG_SPECIFIES_GLOBAL_PROXY, specifiesGlobalProxy);
if (globalProxySpec != null) {
- out.startTag(null, TAG_GLOBAL_PROXY_SPEC);
- out.attribute(null, ATTR_VALUE, globalProxySpec);
- out.endTag(null, TAG_GLOBAL_PROXY_SPEC);
+ writeAttributeValueToXml(out, TAG_GLOBAL_PROXY_SPEC, globalProxySpec);
}
if (globalProxyExclusionList != null) {
- out.startTag(null, TAG_GLOBAL_PROXY_EXCLUSION_LIST);
- out.attribute(null, ATTR_VALUE, globalProxyExclusionList);
- out.endTag(null, TAG_GLOBAL_PROXY_EXCLUSION_LIST);
+ writeAttributeValueToXml(
+ out, TAG_GLOBAL_PROXY_EXCLUSION_LIST, globalProxyExclusionList);
}
}
if (passwordExpirationTimeout != DEF_PASSWORD_EXPIRATION_TIMEOUT) {
- out.startTag(null, TAG_PASSWORD_EXPIRATION_TIMEOUT);
- out.attribute(null, ATTR_VALUE, Long.toString(passwordExpirationTimeout));
- out.endTag(null, TAG_PASSWORD_EXPIRATION_TIMEOUT);
+ writeAttributeValueToXml(
+ out, TAG_PASSWORD_EXPIRATION_TIMEOUT, passwordExpirationTimeout);
}
if (passwordExpirationDate != DEF_PASSWORD_EXPIRATION_DATE) {
- out.startTag(null, TAG_PASSWORD_EXPIRATION_DATE);
- out.attribute(null, ATTR_VALUE, Long.toString(passwordExpirationDate));
- out.endTag(null, TAG_PASSWORD_EXPIRATION_DATE);
+ writeAttributeValueToXml(
+ out, TAG_PASSWORD_EXPIRATION_DATE, passwordExpirationDate);
}
if (encryptionRequested) {
- out.startTag(null, TAG_ENCRYPTION_REQUESTED);
- out.attribute(null, ATTR_VALUE, Boolean.toString(encryptionRequested));
- out.endTag(null, TAG_ENCRYPTION_REQUESTED);
+ writeAttributeValueToXml(
+ out, TAG_ENCRYPTION_REQUESTED, encryptionRequested);
}
if (testOnlyAdmin) {
- out.startTag(null, TAG_TEST_ONLY_ADMIN);
- out.attribute(null, ATTR_VALUE, Boolean.toString(testOnlyAdmin));
- out.endTag(null, TAG_TEST_ONLY_ADMIN);
+ writeAttributeValueToXml(
+ out, TAG_TEST_ONLY_ADMIN, testOnlyAdmin);
}
if (disableCamera) {
- out.startTag(null, TAG_DISABLE_CAMERA);
- out.attribute(null, ATTR_VALUE, Boolean.toString(disableCamera));
- out.endTag(null, TAG_DISABLE_CAMERA);
+ writeAttributeValueToXml(
+ out, TAG_DISABLE_CAMERA, disableCamera);
}
if (disableCallerId) {
- out.startTag(null, TAG_DISABLE_CALLER_ID);
- out.attribute(null, ATTR_VALUE, Boolean.toString(disableCallerId));
- out.endTag(null, TAG_DISABLE_CALLER_ID);
+ writeAttributeValueToXml(
+ out, TAG_DISABLE_CALLER_ID, disableCallerId);
}
if (disableContactsSearch) {
- out.startTag(null, TAG_DISABLE_CONTACTS_SEARCH);
- out.attribute(null, ATTR_VALUE, Boolean.toString(disableContactsSearch));
- out.endTag(null, TAG_DISABLE_CONTACTS_SEARCH);
+ writeAttributeValueToXml(
+ out, TAG_DISABLE_CONTACTS_SEARCH, disableContactsSearch);
}
if (!disableBluetoothContactSharing) {
- out.startTag(null, TAG_DISABLE_BLUETOOTH_CONTACT_SHARING);
- out.attribute(null, ATTR_VALUE,
- Boolean.toString(disableBluetoothContactSharing));
- out.endTag(null, TAG_DISABLE_BLUETOOTH_CONTACT_SHARING);
+ writeAttributeValueToXml(
+ out, TAG_DISABLE_BLUETOOTH_CONTACT_SHARING, disableBluetoothContactSharing);
}
if (disableScreenCapture) {
- out.startTag(null, TAG_DISABLE_SCREEN_CAPTURE);
- out.attribute(null, ATTR_VALUE, Boolean.toString(disableScreenCapture));
- out.endTag(null, TAG_DISABLE_SCREEN_CAPTURE);
+ writeAttributeValueToXml(
+ out, TAG_DISABLE_SCREEN_CAPTURE, disableScreenCapture);
}
if (requireAutoTime) {
- out.startTag(null, TAG_REQUIRE_AUTO_TIME);
- out.attribute(null, ATTR_VALUE, Boolean.toString(requireAutoTime));
- out.endTag(null, TAG_REQUIRE_AUTO_TIME);
+ writeAttributeValueToXml(
+ out, TAG_REQUIRE_AUTO_TIME, requireAutoTime);
}
if (forceEphemeralUsers) {
- out.startTag(null, TAG_FORCE_EPHEMERAL_USERS);
- out.attribute(null, ATTR_VALUE, Boolean.toString(forceEphemeralUsers));
- out.endTag(null, TAG_FORCE_EPHEMERAL_USERS);
+ writeAttributeValueToXml(
+ out, TAG_FORCE_EPHEMERAL_USERS, forceEphemeralUsers);
}
if (isNetworkLoggingEnabled) {
out.startTag(null, TAG_IS_NETWORK_LOGGING_ENABLED);
@@ -1260,15 +1225,13 @@
out.endTag(null, TAG_IS_NETWORK_LOGGING_ENABLED);
}
if (disabledKeyguardFeatures != DEF_KEYGUARD_FEATURES_DISABLED) {
- out.startTag(null, TAG_DISABLE_KEYGUARD_FEATURES);
- out.attribute(null, ATTR_VALUE, Integer.toString(disabledKeyguardFeatures));
- out.endTag(null, TAG_DISABLE_KEYGUARD_FEATURES);
+ writeAttributeValueToXml(
+ out, TAG_DISABLE_KEYGUARD_FEATURES, disabledKeyguardFeatures);
}
if (!accountTypesWithManagementDisabled.isEmpty()) {
- out.startTag(null, TAG_DISABLE_ACCOUNT_MANAGEMENT);
writeAttributeValuesToXml(
- out, TAG_ACCOUNT_TYPE, accountTypesWithManagementDisabled);
- out.endTag(null, TAG_DISABLE_ACCOUNT_MANAGEMENT);
+ out, TAG_DISABLE_ACCOUNT_MANAGEMENT, TAG_ACCOUNT_TYPE,
+ accountTypesWithManagementDisabled);
}
if (!trustAgentInfos.isEmpty()) {
Set<Entry<String, TrustAgentInfo>> set = trustAgentInfos.entrySet();
@@ -1291,9 +1254,9 @@
out.endTag(null, TAG_MANAGE_TRUST_AGENT_FEATURES);
}
if (crossProfileWidgetProviders != null && !crossProfileWidgetProviders.isEmpty()) {
- out.startTag(null, TAG_CROSS_PROFILE_WIDGET_PROVIDERS);
- writeAttributeValuesToXml(out, TAG_PROVIDER, crossProfileWidgetProviders);
- out.endTag(null, TAG_CROSS_PROFILE_WIDGET_PROVIDERS);
+ writeAttributeValuesToXml(
+ out, TAG_CROSS_PROFILE_WIDGET_PROVIDERS, TAG_PROVIDER,
+ crossProfileWidgetProviders);
}
writePackageListToXml(out, TAG_PERMITTED_ACCESSIBILITY_SERVICES,
permittedAccessiblityServices);
@@ -1307,20 +1270,15 @@
out, userRestrictions, TAG_USER_RESTRICTIONS);
}
if (!defaultEnabledRestrictionsAlreadySet.isEmpty()) {
- out.startTag(null, TAG_DEFAULT_ENABLED_USER_RESTRICTIONS);
- writeAttributeValuesToXml(
- out, TAG_RESTRICTION, defaultEnabledRestrictionsAlreadySet);
- out.endTag(null, TAG_DEFAULT_ENABLED_USER_RESTRICTIONS);
+ writeAttributeValuesToXml(out, TAG_DEFAULT_ENABLED_USER_RESTRICTIONS,
+ TAG_RESTRICTION,
+ defaultEnabledRestrictionsAlreadySet);
}
if (!TextUtils.isEmpty(shortSupportMessage)) {
- out.startTag(null, TAG_SHORT_SUPPORT_MESSAGE);
- out.text(shortSupportMessage.toString());
- out.endTag(null, TAG_SHORT_SUPPORT_MESSAGE);
+ writeTextToXml(out, TAG_SHORT_SUPPORT_MESSAGE, shortSupportMessage.toString());
}
if (!TextUtils.isEmpty(longSupportMessage)) {
- out.startTag(null, TAG_LONG_SUPPORT_MESSAGE);
- out.text(longSupportMessage.toString());
- out.endTag(null, TAG_LONG_SUPPORT_MESSAGE);
+ writeTextToXml(out, TAG_LONG_SUPPORT_MESSAGE, longSupportMessage.toString());
}
if (parentAdmin != null) {
out.startTag(null, TAG_PARENT_ADMIN);
@@ -1328,29 +1286,20 @@
out.endTag(null, TAG_PARENT_ADMIN);
}
if (organizationColor != DEF_ORGANIZATION_COLOR) {
- out.startTag(null, TAG_ORGANIZATION_COLOR);
- out.attribute(null, ATTR_VALUE, Integer.toString(organizationColor));
- out.endTag(null, TAG_ORGANIZATION_COLOR);
+ writeAttributeValueToXml(out, TAG_ORGANIZATION_COLOR, organizationColor);
}
if (organizationName != null) {
- out.startTag(null, TAG_ORGANIZATION_NAME);
- out.text(organizationName);
- out.endTag(null, TAG_ORGANIZATION_NAME);
+ writeTextToXml(out, TAG_ORGANIZATION_NAME, organizationName);
}
if (isLogoutEnabled) {
- out.startTag(null, TAG_IS_LOGOUT_ENABLED);
- out.attribute(null, ATTR_VALUE, Boolean.toString(isLogoutEnabled));
- out.endTag(null, TAG_IS_LOGOUT_ENABLED);
+ writeAttributeValueToXml(
+ out, TAG_IS_LOGOUT_ENABLED, isLogoutEnabled);
}
if (startUserSessionMessage != null) {
- out.startTag(null, TAG_START_USER_SESSION_MESSAGE);
- out.text(startUserSessionMessage);
- out.endTag(null, TAG_START_USER_SESSION_MESSAGE);
+ writeTextToXml(out, TAG_START_USER_SESSION_MESSAGE, startUserSessionMessage);
}
if (endUserSessionMessage != null) {
- out.startTag(null, TAG_END_USER_SESSION_MESSAGE);
- out.text(endUserSessionMessage);
- out.endTag(null, TAG_END_USER_SESSION_MESSAGE);
+ writeTextToXml(out, TAG_END_USER_SESSION_MESSAGE, endUserSessionMessage);
}
if (mCrossProfileCalendarPackages == null) {
out.startTag(null, TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL);
@@ -1361,25 +1310,58 @@
}
}
+ void writeTextToXml(XmlSerializer out, String tag, String text) throws IOException {
+ out.startTag(null, tag);
+ out.text(text);
+ out.endTag(null, tag);
+ }
+
void writePackageListToXml(XmlSerializer out, String outerTag,
List<String> packageList)
throws IllegalArgumentException, IllegalStateException, IOException {
if (packageList == null) {
return;
}
-
- out.startTag(null, outerTag);
- writeAttributeValuesToXml(out, TAG_PACKAGE_LIST_ITEM, packageList);
- out.endTag(null, outerTag);
+ writeAttributeValuesToXml(out, outerTag, TAG_PACKAGE_LIST_ITEM, packageList);
}
- void writeAttributeValuesToXml(XmlSerializer out, String tag,
+ void writeAttributeValueToXml(XmlSerializer out, String tag, String value)
+ throws IOException {
+ out.startTag(null, tag);
+ out.attribute(null, ATTR_VALUE, value);
+ out.endTag(null, tag);
+ }
+
+ void writeAttributeValueToXml(XmlSerializer out, String tag, int value)
+ throws IOException {
+ out.startTag(null, tag);
+ out.attribute(null, ATTR_VALUE, Integer.toString(value));
+ out.endTag(null, tag);
+ }
+
+ void writeAttributeValueToXml(XmlSerializer out, String tag, long value)
+ throws IOException {
+ out.startTag(null, tag);
+ out.attribute(null, ATTR_VALUE, Long.toString(value));
+ out.endTag(null, tag);
+ }
+
+ void writeAttributeValueToXml(XmlSerializer out, String tag, boolean value)
+ throws IOException {
+ out.startTag(null, tag);
+ out.attribute(null, ATTR_VALUE, Boolean.toString(value));
+ out.endTag(null, tag);
+ }
+
+ void writeAttributeValuesToXml(XmlSerializer out, String outerTag, String innerTag,
@NonNull Collection<String> values) throws IOException {
+ out.startTag(null, outerTag);
for (String value : values) {
- out.startTag(null, tag);
+ out.startTag(null, innerTag);
out.attribute(null, ATTR_VALUE, value);
- out.endTag(null, tag);
+ out.endTag(null, innerTag);
}
+ out.endTag(null, outerTag);
}
void readFromXml(XmlPullParser parser, boolean shouldOverridePolicies)
@@ -1999,6 +1981,10 @@
return LocalServices.getService(LockSettingsInternal.class);
}
+ boolean hasUserSetupCompleted(DevicePolicyData userData) {
+ return userData.mUserSetupComplete;
+ }
+
boolean isBuildDebuggable() {
return Build.IS_DEBUGGABLE;
}
@@ -5659,7 +5645,7 @@
KeyChain.bindAsUser(mContext, UserHandle.getUserHandleForUid(callingUid));
try {
IKeyChainService keyChain = keyChainConnection.getService();
- if (!keyChain.installKeyPair(privKey, cert, chain, alias)) {
+ if (!keyChain.installKeyPair(privKey, cert, chain, alias, KeyStore.UID_SELF)) {
return false;
}
if (requestAccess) {
@@ -8271,7 +8257,7 @@
if (!mHasFeature) {
return true;
}
- return getUserData(userHandle).mUserSetupComplete;
+ return mInjector.hasUserSetupCompleted(getUserData(userHandle));
}
private boolean hasPaired(int userHandle) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/AccessibilityGestureDetectorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/AccessibilityGestureDetectorTest.java
index 2585a28..b707912 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/AccessibilityGestureDetectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/AccessibilityGestureDetectorTest.java
@@ -176,6 +176,6 @@
// Check that correct gesture was recognized.
verify(mResultListener).onGestureCompleted(
- argThat(gestureInfo -> gestureInfo.getGestureId() == gestureId));
+ argThat(gestureEvent -> gestureEvent.getGestureId() == gestureId));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java b/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java
index 6a0d7f1..9e3b54d 100644
--- a/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java
@@ -22,7 +22,6 @@
import static com.android.server.am.MemoryStatUtil.parseCmdlineFromProcfs;
import static com.android.server.am.MemoryStatUtil.parseMemoryStatFromMemcg;
import static com.android.server.am.MemoryStatUtil.parseMemoryStatFromProcfs;
-import static com.android.server.am.MemoryStatUtil.parseVmHWMFromProcfs;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@@ -230,18 +229,6 @@
}
@Test
- public void testParseVmHWMFromProcfs_parsesCorrectValue() {
- assertEquals(137668, parseVmHWMFromProcfs(PROC_STATUS_CONTENTS));
- }
-
- @Test
- public void testParseVmHWMFromProcfs_emptyContents() {
- assertEquals(0, parseVmHWMFromProcfs(""));
-
- assertEquals(0, parseVmHWMFromProcfs(null));
- }
-
- @Test
public void testParseCmdlineFromProcfs_invalidValue() {
byte[] nothing = new byte[] {0x00, 0x74, 0x65, 0x73, 0x74}; // \0test
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 d6cb9826..d900910 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -765,7 +765,38 @@
}
/**
- * Test for: @{link DevicePolicyManager#reportPasswordChanged}
+ * Test for: {@link DevicePolicyManager#setPasswordHistoryLength(ComponentName, int)}
+ *
+ * Validates that when the password history length is set, it is persisted after rebooting
+ */
+ public void testSaveAndLoadPasswordHistoryLength_persistedAfterReboot() throws Exception {
+ int passwordHistoryLength = 2;
+
+ mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS);
+ mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+
+ // Install admin1 on system user.
+ setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID);
+
+ // Set admin1 to active admin and device owner
+ dpm.setActiveAdmin(admin1, false);
+ dpm.setDeviceOwner(admin1, null, UserHandle.USER_SYSTEM);
+
+ // Save password history length
+ dpm.setPasswordHistoryLength(admin1, passwordHistoryLength);
+
+ assertEquals(dpm.getPasswordHistoryLength(admin1), passwordHistoryLength);
+
+ initializeDpms();
+ reset(mContext.spiedContext);
+
+ // Password history length should persist after rebooted
+ assertEquals(dpm.getPasswordHistoryLength(admin1), passwordHistoryLength);
+ }
+
+ /**
+ * Test for: {@link DevicePolicyManager#reportPasswordChanged}
*
* Validates that when the password for a user changes, the notification broadcast intent
* {@link DeviceAdminReceiver#ACTION_PASSWORD_CHANGED} is sent to managed profile owners, in
diff --git a/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java b/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java
index bd3d9ab..3852b9f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java
@@ -17,6 +17,7 @@
import android.content.Context;
import android.content.pm.ModuleInfo;
+import android.content.pm.PackageManager;
import android.test.InstrumentationTestCase;
import com.android.frameworks.servicestests.R;
@@ -28,7 +29,7 @@
public void testSuccessfulParse() {
ModuleInfoProvider provider = getProvider(R.xml.well_formed_metadata);
- List<ModuleInfo> mi = provider.getInstalledModules(0);
+ List<ModuleInfo> mi = provider.getInstalledModules(PackageManager.MATCH_ALL);
assertEquals(2, mi.size());
Collections.sort(mi, (ModuleInfo m1, ModuleInfo m2) ->
@@ -49,18 +50,18 @@
public void testParseFailure_incorrectTopLevelElement() {
ModuleInfoProvider provider = getProvider(R.xml.unparseable_metadata1);
- assertEquals(0, provider.getInstalledModules(0).size());
+ assertEquals(0, provider.getInstalledModules(PackageManager.MATCH_ALL).size());
}
public void testParseFailure_incorrectModuleElement() {
ModuleInfoProvider provider = getProvider(R.xml.unparseable_metadata2);
- assertEquals(0, provider.getInstalledModules(0).size());
+ assertEquals(0, provider.getInstalledModules(PackageManager.MATCH_ALL).size());
}
public void testParse_unknownAttributesIgnored() {
ModuleInfoProvider provider = getProvider(R.xml.well_formed_metadata);
- List<ModuleInfo> mi = provider.getInstalledModules(0);
+ List<ModuleInfo> mi = provider.getInstalledModules(PackageManager.MATCH_ALL);
assertEquals(2, mi.size());
ModuleInfo mi1 = provider.getModuleInfo("com.android.module1", 0);
diff --git a/services/tests/servicestests/src/com/android/server/stats/ProcfsMemoryUtilTest.java b/services/tests/servicestests/src/com/android/server/stats/ProcfsMemoryUtilTest.java
new file mode 100644
index 0000000..4fb533f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/stats/ProcfsMemoryUtilTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2019 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.stats;
+
+import static com.android.server.stats.ProcfsMemoryUtil.parseVmHWMFromStatus;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:ProcfsMemoryUtilTest
+ */
+@SmallTest
+public class ProcfsMemoryUtilTest {
+ private static final String STATUS_CONTENTS = "Name:\tandroid.youtube\n"
+ + "State:\tS (sleeping)\n"
+ + "Tgid:\t12088\n"
+ + "Pid:\t12088\n"
+ + "PPid:\t723\n"
+ + "TracerPid:\t0\n"
+ + "Uid:\t10083\t10083\t10083\t10083\n"
+ + "Gid:\t10083\t10083\t10083\t10083\n"
+ + "Ngid:\t0\n"
+ + "FDSize:\t128\n"
+ + "Groups:\t3003 9997 20083 50083 \n"
+ + "VmPeak:\t 4546844 kB\n"
+ + "VmSize:\t 4542636 kB\n"
+ + "VmLck:\t 0 kB\n"
+ + "VmPin:\t 0 kB\n"
+ + "VmHWM:\t 137668 kB\n" // RSS high-water mark
+ + "VmRSS:\t 126776 kB\n" // RSS
+ + "RssAnon:\t 37860 kB\n"
+ + "RssFile:\t 88764 kB\n"
+ + "RssShmem:\t 152 kB\n"
+ + "VmData:\t 4125112 kB\n"
+ + "VmStk:\t 8192 kB\n"
+ + "VmExe:\t 24 kB\n"
+ + "VmLib:\t 102432 kB\n"
+ + "VmPTE:\t 1300 kB\n"
+ + "VmPMD:\t 36 kB\n"
+ + "VmSwap:\t 22 kB\n" // Swap
+ + "Threads:\t95\n"
+ + "SigQ:\t0/13641\n"
+ + "SigPnd:\t0000000000000000\n"
+ + "ShdPnd:\t0000000000000000\n"
+ + "SigBlk:\t0000000000001204\n"
+ + "SigIgn:\t0000000000000001\n"
+ + "SigCgt:\t00000006400084f8\n"
+ + "CapInh:\t0000000000000000\n"
+ + "CapPrm:\t0000000000000000\n"
+ + "CapEff:\t0000000000000000\n"
+ + "CapBnd:\t0000000000000000\n"
+ + "CapAmb:\t0000000000000000\n"
+ + "Seccomp:\t2\n"
+ + "Cpus_allowed:\tff\n"
+ + "Cpus_allowed_list:\t0-7\n"
+ + "Mems_allowed:\t1\n"
+ + "Mems_allowed_list:\t0\n"
+ + "voluntary_ctxt_switches:\t903\n"
+ + "nonvoluntary_ctxt_switches:\t104\n";
+
+ @Test
+ public void testParseVmHWMFromStatus_parsesCorrectValue() {
+ assertThat(parseVmHWMFromStatus(STATUS_CONTENTS)).isEqualTo(137668);
+ }
+
+ @Test
+ public void testParseVmHWMFromStatus_invalidValue() {
+ assertThat(parseVmHWMFromStatus("test\nVmHWM: x0x0x\ntest")).isEqualTo(0);
+ }
+
+ @Test
+ public void testParseVmHWMFromStatus_emptyContents() {
+ assertThat(parseVmHWMFromStatus("")).isEqualTo(0);
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
index e15af3d..0b4760d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
@@ -68,6 +68,7 @@
private final int uid2 = 1111111;
private static final String TEST_CHANNEL_ID = "test_channel_id";
+ private NotificationRecord mRecordMinCallNonInterruptive;
private NotificationRecord mRecordMinCall;
private NotificationRecord mRecordHighCall;
private NotificationRecord mRecordDefaultMedia;
@@ -105,6 +106,18 @@
smsPkg = Settings.Secure.getString(mContext.getContentResolver(),
Settings.Secure.SMS_DEFAULT_APPLICATION);
+ Notification nonInterruptiveNotif = new Notification.Builder(mContext, TEST_CHANNEL_ID)
+ .setCategory(Notification.CATEGORY_CALL)
+ .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
+ .build();
+ mRecordMinCallNonInterruptive = new NotificationRecord(mContext,
+ new StatusBarNotification(callPkg,
+ callPkg, 1, "mRecordMinCallNonInterruptive", callUid, callUid,
+ nonInterruptiveNotif,
+ new UserHandle(userId), "", 2000), getDefaultChannel());
+ mRecordMinCallNonInterruptive.setSystemImportance(NotificationManager.IMPORTANCE_MIN);
+ mRecordMinCallNonInterruptive.setInterruptive(false);
+
Notification n1 = new Notification.Builder(mContext, TEST_CHANNEL_ID)
.setCategory(Notification.CATEGORY_CALL)
.setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
@@ -113,6 +126,7 @@
callPkg, 1, "minCall", callUid, callUid, n1,
new UserHandle(userId), "", 2000), getDefaultChannel());
mRecordMinCall.setSystemImportance(NotificationManager.IMPORTANCE_MIN);
+ mRecordMinCall.setInterruptive(true);
Notification n2 = new Notification.Builder(mContext, TEST_CHANNEL_ID)
.setCategory(Notification.CATEGORY_CALL)
@@ -245,6 +259,7 @@
expected.add(mRecordCheater);
expected.add(mRecordCheaterColorized);
expected.add(mRecordMinCall);
+ expected.add(mRecordMinCallNonInterruptive);
List<NotificationRecord> actual = new ArrayList<>();
actual.addAll(expected);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
index 397d215..a9fe1a6 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -51,6 +51,8 @@
import android.service.notification.SnoozeCriterion;
import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.server.UiServiceTestCase;
import org.junit.After;
@@ -61,8 +63,6 @@
import java.util.ArrayList;
import java.util.List;
-import androidx.test.runner.AndroidJUnit4;
-
@SmallTest
@RunWith(AndroidJUnit4.class)
public class NotificationListenerServiceTest extends UiServiceTestCase {
@@ -116,6 +116,7 @@
assertActionsEqual(getSmartActions(key, i), ranking.getSmartActions());
assertEquals(getSmartReplies(key, i), ranking.getSmartReplies());
assertEquals(canBubble(i), ranking.canBubble());
+ assertEquals(visuallyInterruptive(i), ranking.visuallyInterruptive());
}
}
@@ -182,7 +183,8 @@
tweak.isNoisy(),
(ArrayList) tweak.getSmartActions(),
(ArrayList) tweak.getSmartReplies(),
- tweak.canBubble()
+ tweak.canBubble(),
+ tweak.visuallyInterruptive()
);
assertNotEquals(nru, nru2);
}
@@ -258,7 +260,8 @@
getNoisy(i),
getSmartActions(key, i),
getSmartReplies(key, i),
- canBubble(i)
+ canBubble(i),
+ visuallyInterruptive(i)
);
rankings[i] = ranking;
}
@@ -363,6 +366,10 @@
return index % 4 == 0;
}
+ private boolean visuallyInterruptive(int index) {
+ return index % 4 == 0;
+ }
+
private void assertActionsEqual(
List<Notification.Action> expecteds, List<Notification.Action> actuals) {
assertEquals(expecteds.size(), actuals.size());
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 60730fe..1f672c0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -354,7 +354,7 @@
doReturn(stack).when(mRootActivityContainer)
.getLaunchStack(any(), any(), any(), anyBoolean());
doReturn(stack).when(mRootActivityContainer)
- .getLaunchStack(any(), any(), any(), anyBoolean(), any());
+ .getLaunchStack(any(), any(), any(), anyBoolean(), any(), anyInt(), anyInt());
}
// Set up mock package manager internal and make sure no unmocked methods are called
@@ -366,7 +366,7 @@
// Never review permissions
doReturn(false).when(mockPackageManager).isPermissionsReviewRequired(any(), anyInt());
- doNothing().when(mockPackageManager).grantEphemeralAccess(
+ doNothing().when(mockPackageManager).grantImplicitAccess(
anyInt(), any(), anyInt(), anyInt());
final Intent intent = new Intent();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
index 1731f7c..4f00383 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
@@ -38,6 +38,7 @@
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
+import android.graphics.Rect;
import android.os.UserHandle;
import android.service.voice.IVoiceInteractionSession;
import android.testing.DexmakerShareClassLoaderRule;
@@ -139,6 +140,8 @@
private int mScreenOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
private boolean mLaunchTaskBehind;
private int mConfigChanges;
+ private int mLaunchedFromPid;
+ private int mLaunchedFromUid;
ActivityBuilder(ActivityTaskManagerService service) {
mService = service;
@@ -214,6 +217,16 @@
return this;
}
+ ActivityBuilder setLaunchedFromPid(int pid) {
+ mLaunchedFromPid = pid;
+ return this;
+ }
+
+ ActivityBuilder setLaunchedFromUid(int uid) {
+ mLaunchedFromUid = uid;
+ return this;
+ }
+
ActivityRecord build() {
if (mComponent == null) {
final int id = sCurrentActivityId++;
@@ -250,10 +263,11 @@
}
final ActivityRecord activity = new ActivityRecord(mService, null /* caller */,
- 0 /* launchedFromPid */, 0, null, intent, null,
- aInfo /*aInfo*/, new Configuration(), null /* resultTo */, null /* resultWho */,
- 0 /* reqCode */, false /*componentSpecified*/, false /* rootVoiceInteraction */,
- mService.mStackSupervisor, options, null /* sourceRecord */);
+ mLaunchedFromPid /* launchedFromPid */, mLaunchedFromUid /* launchedFromUid */,
+ null, intent, null, aInfo /*aInfo*/, new Configuration(), null /* resultTo */,
+ null /* resultWho */, 0 /* reqCode */, false /*componentSpecified*/,
+ false /* rootVoiceInteraction */, mService.mStackSupervisor, options,
+ null /* sourceRecord */);
spyOn(activity);
if (mTaskRecord != null) {
// fullscreen value is normally read from resources in ctor, so for testing we need
@@ -432,7 +446,12 @@
final ActivityStackSupervisor supervisor = mRootActivityContainer.mStackSupervisor;
if (mWindowingMode == WINDOWING_MODE_PINNED) {
stack = new ActivityStack(mDisplay, stackId, supervisor,
- mWindowingMode, ACTIVITY_TYPE_STANDARD, mOnTop);
+ mWindowingMode, ACTIVITY_TYPE_STANDARD, mOnTop) {
+ @Override
+ Rect getDefaultPictureInPictureBounds(float aspectRatio) {
+ return new Rect(50, 50, 100, 100);
+ }
+ };
} else {
stack = new ActivityStack(mDisplay, stackId, supervisor,
mWindowingMode, mActivityType, mOnTop);
diff --git a/services/tests/wmtests/src/com/android/server/wm/PinnedStackControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/PinnedStackControllerTest.java
index e9c2263..efd468f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/PinnedStackControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/PinnedStackControllerTest.java
@@ -66,8 +66,8 @@
verify(mIPinnedStackListener).onImeVisibilityChanged(false, 0);
verify(mIPinnedStackListener).onShelfVisibilityChanged(false, 0);
- verify(mIPinnedStackListener).onMovementBoundsChanged(any(), eq(false),
- eq(false));
+ verify(mIPinnedStackListener).onMovementBoundsChanged(any(), any(), any(), eq(false),
+ eq(false), anyInt());
verify(mIPinnedStackListener).onActionsChanged(any());
verify(mIPinnedStackListener).onMinimizedStateChanged(anyBoolean());
@@ -75,8 +75,8 @@
mWm.setShelfHeight(true, SHELF_HEIGHT);
verify(mIPinnedStackListener).onShelfVisibilityChanged(true, SHELF_HEIGHT);
- verify(mIPinnedStackListener).onMovementBoundsChanged(any(), eq(false),
- eq(true));
+ verify(mIPinnedStackListener).onMovementBoundsChanged(any(), any(), any(), eq(false),
+ eq(true), anyInt());
verify(mIPinnedStackListener, never()).onImeVisibilityChanged(anyBoolean(), anyInt());
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
index c67b860..aa97de72 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
@@ -25,6 +25,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.TYPE_VIRTUAL;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
@@ -61,6 +62,7 @@
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.util.Pair;
+import android.view.DisplayInfo;
import androidx.test.filters.MediumTest;
@@ -820,6 +822,41 @@
}
/**
+ * Test that {@link RootActivityContainer#getLaunchStack} with the real caller id will get the
+ * expected stack when requesting the activity launch on the secondary display.
+ */
+ @Test
+ public void testGetLaunchStackWithRealCallerId() {
+ // Create a non-system owned virtual display.
+ final DisplayInfo info = new DisplayInfo();
+ mSupervisor.mService.mContext.getDisplay().getDisplayInfo(info);
+ info.type = TYPE_VIRTUAL;
+ info.ownerUid = 100;
+ final TestActivityDisplay secondaryDisplay = TestActivityDisplay.create(mSupervisor, info);
+ mRootActivityContainer.addChild(secondaryDisplay, POSITION_TOP);
+
+ // Create an activity with specify the original launch pid / uid.
+ final ActivityRecord r = new ActivityBuilder(mService).setLaunchedFromPid(200)
+ .setLaunchedFromUid(200).build();
+
+ // Simulate ActivityStarter to find a launch stack for requesting the activity to launch
+ // on the secondary display with realCallerId.
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(secondaryDisplay.mDisplayId);
+ options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ doReturn(true).when(mSupervisor).canPlaceEntityOnDisplay(secondaryDisplay.mDisplayId,
+ 300 /* test realCallerPid */, 300 /* test realCallerUid */, r.info);
+ final ActivityStack result = mRootActivityContainer.getLaunchStack(r, options,
+ null /* task */, true /* onTop */, null, 300 /* test realCallerPid */,
+ 300 /* test realCallerUid */);
+
+ // Assert that the stack is returned as expected.
+ assertNotNull(result);
+ assertEquals("The display ID of the stack should same as secondary display ",
+ secondaryDisplay.mDisplayId, result.mDisplayId);
+ }
+
+ /**
* Mock {@link RootActivityContainer#resolveHomeActivity} for returning consistent activity
* info for test cases (the original implementation will resolve from the real package manager).
*/
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 2fe2c41..5a4d399 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -231,7 +231,7 @@
new AMTestInjector(mContext, mHandlerThread), mHandlerThread);
spyOn(mAmService);
doReturn(mock(IPackageManager.class)).when(mAmService).getPackageManager();
- doNothing().when(mAmService).grantEphemeralAccessLocked(
+ doNothing().when(mAmService).grantImplicitAccess(
anyInt(), any(), anyInt(), anyInt());
// ActivityManagerInternal
diff --git a/startop/apps/test/src/SystemServerBenchmarkActivity.java b/startop/apps/test/src/SystemServerBenchmarkActivity.java
index 61d4322..ed3cbe9 100644
--- a/startop/apps/test/src/SystemServerBenchmarkActivity.java
+++ b/startop/apps/test/src/SystemServerBenchmarkActivity.java
@@ -17,8 +17,10 @@
package com.android.startop.test;
import android.app.Activity;
+import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -139,6 +141,10 @@
throw new RuntimeException(e);
}
});
+
+ new Benchmark(benchmarkList, "getPackagesForUid", () -> {
+ pm.getPackagesForUid(app.uid);
+ });
} catch (NameNotFoundException e) {
throw new RuntimeException(e);
}
@@ -152,5 +158,45 @@
}
});
+ new Benchmark(benchmarkList, "getLaunchIntentForPackage", () -> {
+ pm.getLaunchIntentForPackage("com.android.startop.test");
+ });
+
+ new Benchmark(benchmarkList, "getPackageUid", () -> {
+ try {
+ pm.getPackageUid("com.android.startop.test", 0);
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ });
+
+ new Benchmark(benchmarkList, "checkPermission", () -> {
+ // Check for the first permission I could find.
+ pm.checkPermission("android.permission.SEND_SMS", "com.android.startop.test");
+ });
+
+ new Benchmark(benchmarkList, "checkSignatures", () -> {
+ // Compare with settings, since settings is on both AOSP and Master builds
+ pm.checkSignatures("com.android.settings", "com.android.startop.test");
+ });
+
+ Intent intent = new Intent(Intent.ACTION_BOOT_COMPLETED);
+ new Benchmark(benchmarkList, "queryBroadcastReceivers", () -> {
+ pm.queryBroadcastReceivers(intent, 0);
+ });
+
+ new Benchmark(benchmarkList, "hasSystemFeature", () -> {
+ pm.hasSystemFeature(PackageManager.FEATURE_CAMERA);
+ });
+
+ new Benchmark(benchmarkList, "resolveService", () -> {
+ pm.resolveService(intent, 0);
+ });
+
+ ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
+ new Benchmark(benchmarkList, "getRunningAppProcesses", () -> {
+ am.getRunningAppProcesses();
+ });
+
}
}
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 3548810..0abd9fc 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -30,6 +30,7 @@
import android.os.RemoteException;
import android.telecom.Logging.Session;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
import com.android.internal.telecom.IConnectionService;
import com.android.internal.telecom.IConnectionServiceAdapter;
@@ -2672,4 +2673,13 @@
return ++mId;
}
}
+
+ /**
+ * Returns this handler, ONLY FOR TESTING.
+ * @hide
+ */
+ @VisibleForTesting
+ public Handler getHandler() {
+ return mHandler;
+ }
}
diff --git a/telecomm/java/android/telecom/Logging/SessionManager.java b/telecomm/java/android/telecom/Logging/SessionManager.java
index 949f7b7..49c3a72 100644
--- a/telecomm/java/android/telecom/Logging/SessionManager.java
+++ b/telecomm/java/android/telecom/Logging/SessionManager.java
@@ -391,6 +391,20 @@
return mCurrentThreadId.get();
}
+ /**
+ * @return A String representation of the active sessions at the time that this method is
+ * called.
+ */
+ @VisibleForTesting
+ public synchronized String printActiveSessions() {
+ StringBuilder message = new StringBuilder();
+ for (ConcurrentHashMap.Entry<Integer, Session> entry : mSessionMapper.entrySet()) {
+ message.append(entry.getValue().printFullSessionTree());
+ message.append("\n");
+ }
+ return message.toString();
+ }
+
@VisibleForTesting
public synchronized void cleanupStaleSessions(long timeoutMs) {
String logMessage = "Stale Sessions Cleaned:\n";
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index 58f2858..f527bc3 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -578,7 +578,8 @@
PackageManager packageManager = context.getPackageManager();
PackageInfo packageInfo;
try {
- packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
+ packageInfo = packageManager.getPackageInfo(packageName,
+ PackageManager.GET_SIGNING_CERTIFICATES);
} catch (PackageManager.NameNotFoundException e) {
Log.d("SubscriptionInfo", "canManageSubscription: Unknown package: " + packageName, e);
return false;
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index d3cba2e..51de903 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -2701,7 +2701,8 @@
PackageManager packageManager = mContext.getPackageManager();
PackageInfo packageInfo;
try {
- packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
+ packageInfo = packageManager.getPackageInfo(packageName,
+ PackageManager.GET_SIGNING_CERTIFICATES);
} catch (PackageManager.NameNotFoundException e) {
logd("Unknown package: " + packageName);
return false;
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index f25b012..b5dbbe7 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -3251,6 +3251,31 @@
}
}
+
+ /**
+ * Returns true if the specified type of application (e.g. {@link #APPTYPE_CSIM} is present
+ * on the UICC card.
+ *
+ * Requires that the calling app has READ_PRIVILEGED_PHONE_STATE permission
+ *
+ * @param appType the uicc app type like {@link APPTYPE_CSIM}
+ * @return true if the specified type of application in UICC CARD or false if no uicc or error.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public boolean isApplicationOnUicc(@UiccAppType int appType) {
+ try {
+ ITelephony service = getITelephony();
+ if (service != null) {
+ return service.isApplicationOnUicc(getSubId(), appType);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#isApplicationOnUicc", e);
+ }
+ return false;
+ }
+
/**
* Returns a constant indicating the state of the device SIM card in a logical slot.
*
diff --git a/telephony/java/android/telephony/UiccAccessRule.java b/telephony/java/android/telephony/UiccAccessRule.java
index 811722f..93ccba1 100644
--- a/telephony/java/android/telephony/UiccAccessRule.java
+++ b/telephony/java/android/telephony/UiccAccessRule.java
@@ -20,6 +20,7 @@
import android.annotation.SystemApi;
import android.content.pm.PackageInfo;
import android.content.pm.Signature;
+import android.content.pm.SigningInfo;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -169,17 +170,28 @@
*
* @param packageInfo package info fetched from
* {@link android.content.pm.PackageManager#getPackageInfo}.
- * {@link android.content.pm.PackageManager#GET_SIGNATURES} must have been passed in.
+ * {@link android.content.pm.PackageManager#GET_SIGNING_CERTIFICATES} must have been
+ * passed in.
* @return either {@link TelephonyManager#CARRIER_PRIVILEGE_STATUS_HAS_ACCESS} or
* {@link TelephonyManager#CARRIER_PRIVILEGE_STATUS_NO_ACCESS}.
*/
public int getCarrierPrivilegeStatus(PackageInfo packageInfo) {
- if (packageInfo.signatures == null || packageInfo.signatures.length == 0) {
- throw new IllegalArgumentException(
- "Must use GET_SIGNATURES when looking up package info");
+ Signature[] signatures = packageInfo.signatures;
+ SigningInfo sInfo = packageInfo.signingInfo;
+
+ if (sInfo != null) {
+ signatures = sInfo.getSigningCertificateHistory();
+ if (sInfo.hasMultipleSigners()) {
+ signatures = sInfo.getApkContentsSigners();
+ }
}
- for (Signature sig : packageInfo.signatures) {
+ if (signatures == null || signatures.length == 0) {
+ throw new IllegalArgumentException(
+ "Must use GET_SIGNING_CERTIFICATES when looking up package info");
+ }
+
+ for (Signature sig : signatures) {
int accessStatus = getCarrierPrivilegeStatus(sig, packageInfo.packageName);
if (accessStatus != TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS) {
return accessStatus;
diff --git a/telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl b/telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl
index 606df15..6a35e33 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl
@@ -22,7 +22,6 @@
*/
oneway interface IImsSmsListener {
void onSendSmsResult(int token, int messageRef, int status, int reason);
- void onSmsStatusReportReceived(int token, int messageRef, in String format,
- in byte[] pdu);
+ void onSmsStatusReportReceived(int token, in String format, in byte[] pdu);
void onSmsReceived(int token, in String format, in byte[] pdu);
-}
\ No newline at end of file
+}
diff --git a/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
index 852c8e0..2e4bfb3 100644
--- a/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
@@ -147,7 +147,7 @@
* {@link SmsMessage#FORMAT_3GPP2}.
* @param smsc the Short Message Service Center address.
* @param isRetry whether it is a retry of an already attempted message or not.
- * @param pdu PDUs representing the contents of the message.
+ * @param pdu PDU representing the contents of the message.
*/
public void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry,
byte[] pdu) {
@@ -166,27 +166,29 @@
* provider.
*
* @param token token provided in {@link #onSmsReceived(int, String, byte[])}
+ * @param messageRef the message reference
* @param result result of delivering the message. Valid values are:
* {@link #DELIVER_STATUS_OK},
* {@link #DELIVER_STATUS_ERROR_GENERIC},
* {@link #DELIVER_STATUS_ERROR_NO_MEMORY},
* {@link #DELIVER_STATUS_ERROR_REQUEST_NOT_SUPPORTED}
- * @param messageRef the message reference
*/
- public void acknowledgeSms(int token, @DeliverStatusResult int messageRef, int result) {
+ public void acknowledgeSms(int token, int messageRef, @DeliverStatusResult int result) {
Log.e(LOG_TAG, "acknowledgeSms() not implemented.");
}
/**
* This method will be triggered by the platform after
- * {@link #onSmsStatusReportReceived(int, int, String, byte[])} has been called to provide the
+ * {@link #onSmsStatusReportReceived(int, int, String, byte[])} or
+ * {@link #onSmsStatusReportReceived(int, String, byte[])} has been called to provide the
* result to the IMS provider.
*
- * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
+ * @param token token provided in {@link #onSmsStatusReportReceived(int, int, String, byte[])}
+ * or {@link #onSmsStatusReportReceived(int, String, byte[])}
+ * @param messageRef the message reference
* @param result result of delivering the message. Valid values are:
* {@link #STATUS_REPORT_STATUS_OK},
* {@link #STATUS_REPORT_STATUS_ERROR}
- * @param messageRef the message reference
*/
public void acknowledgeSmsReport(int token, int messageRef, @StatusReportResult int result) {
Log.e(LOG_TAG, "acknowledgeSmsReport() not implemented.");
@@ -204,7 +206,7 @@
* callbacks for this message.
* @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
* {@link SmsMessage#FORMAT_3GPP2}.
- * @param pdu PDUs representing the contents of the message.
+ * @param pdu PDU representing the contents of the message.
* @throws RuntimeException if called before {@link #onReady()} is triggered.
*/
public final void onSmsReceived(int token, String format, byte[] pdu) throws RuntimeException {
@@ -285,23 +287,32 @@
}
/**
- * Sets the status report of the sent message.
+ * This method should be triggered by the IMS providers when the status report of the sent
+ * message is received. The platform will handle the report and notify the IMS provider of the
+ * result by calling {@link #acknowledgeSmsReport(int, int, int)}.
*
+ * This method must not be called before {@link #onReady()} is called or the call will fail. If
+ * the platform is not available, {@link #acknowledgeSmsReport(int, int, int)} will be called
+ * with the {@link #STATUS_REPORT_STATUS_ERROR} result code.
* @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
* @param messageRef the message reference.
* @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
- * {@link SmsMessage#FORMAT_3GPP2}.
- * @param pdu PDUs representing the content of the status report.
+ * {@link SmsMessage#FORMAT_3GPP2}.
+ * @param pdu PDU representing the content of the status report.
* @throws RuntimeException if called before {@link #onReady()} is triggered
+ *
+ * @deprecated Use {@link #onSmsStatusReportReceived(int, String, byte[])} instead without the
+ * message reference.
*/
+ @Deprecated
public final void onSmsStatusReportReceived(int token, int messageRef, String format,
- byte[] pdu) throws RuntimeException{
+ byte[] pdu) throws RuntimeException {
synchronized (mLock) {
if (mListener == null) {
throw new RuntimeException("Feature not ready.");
}
try {
- mListener.onSmsStatusReportReceived(token, messageRef, format, pdu);
+ mListener.onSmsStatusReportReceived(token, format, pdu);
} catch (RemoteException e) {
Log.e(LOG_TAG, "Can not process sms status report: " + e.getMessage());
acknowledgeSmsReport(token, messageRef, STATUS_REPORT_STATUS_ERROR);
@@ -310,6 +321,46 @@
}
/**
+ * This method should be triggered by the IMS providers when the status report of the sent
+ * message is received. The platform will handle the report and notify the IMS provider of the
+ * result by calling {@link #acknowledgeSmsReport(int, int, int)}.
+ *
+ * This method must not be called before {@link #onReady()} is called or the call will fail. If
+ * the platform is not available, {@link #acknowledgeSmsReport(int, int, int)} will be called
+ * with the {@link #STATUS_REPORT_STATUS_ERROR} result code.
+ * @param token unique token generated by IMS providers that the platform will use to trigger
+ * callbacks for this message.
+ * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
+ * {@link SmsMessage#FORMAT_3GPP2}.
+ * @param pdu PDU representing the content of the status report.
+ * @throws RuntimeException if called before {@link #onReady()} is triggered
+ */
+ public final void onSmsStatusReportReceived(int token, String format, byte[] pdu)
+ throws RuntimeException {
+ synchronized (mLock) {
+ if (mListener == null) {
+ throw new RuntimeException("Feature not ready.");
+ }
+ try {
+ mListener.onSmsStatusReportReceived(token, format, pdu);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Can not process sms status report: " + e.getMessage());
+ SmsMessage message = SmsMessage.createFromPdu(pdu, format);
+ if (message != null && message.mWrappedSmsMessage != null) {
+ acknowledgeSmsReport(
+ token,
+ message.mWrappedSmsMessage.mMessageRef,
+ STATUS_REPORT_STATUS_ERROR);
+ } else {
+ Log.w(LOG_TAG,
+ "onSmsStatusReportReceivedWithoutMessageRef: Invalid pdu entered.");
+ acknowledgeSmsReport(token, 0, STATUS_REPORT_STATUS_ERROR);
+ }
+ }
+ }
+ }
+
+ /**
* Returns the SMS format. Default is {@link SmsMessage#FORMAT_3GPP} unless overridden by IMS
* Provider.
*
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 9f1a2f7..866e936 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2012,6 +2012,13 @@
*/
int getRadioHalVersion();
+ /**
+ * Returns true if the specified type of application (e.g. {@link #APPTYPE_CSIM} is present
+ * on the UICC card.
+ * @hide
+ */
+ boolean isApplicationOnUicc(int subId, int appType);
+
boolean isModemEnabledForSlot(int slotIndex, String callingPackage);
boolean isDataEnabledForApn(int apnType, int subId, String callingPackage);
diff --git a/telephony/java/com/google/android/mms/ContentType.java b/telephony/java/com/google/android/mms/ContentType.java
new file mode 100644
index 0000000..12e4b7e
--- /dev/null
+++ b/telephony/java/com/google/android/mms/ContentType.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-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.google.android.mms;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import java.util.ArrayList;
+
+public class ContentType {
+ public static final String MMS_MESSAGE = "application/vnd.wap.mms-message";
+ // The phony content type for generic PDUs (e.g. ReadOrig.ind,
+ // Notification.ind, Delivery.ind).
+ public static final String MMS_GENERIC = "application/vnd.wap.mms-generic";
+ public static final String MULTIPART_MIXED = "application/vnd.wap.multipart.mixed";
+ public static final String MULTIPART_RELATED = "application/vnd.wap.multipart.related";
+ public static final String MULTIPART_ALTERNATIVE = "application/vnd.wap.multipart.alternative";
+
+ public static final String TEXT_PLAIN = "text/plain";
+ public static final String TEXT_HTML = "text/html";
+ public static final String TEXT_VCALENDAR = "text/x-vCalendar";
+ public static final String TEXT_VCARD = "text/x-vCard";
+
+ public static final String IMAGE_UNSPECIFIED = "image/*";
+ public static final String IMAGE_JPEG = "image/jpeg";
+ public static final String IMAGE_JPG = "image/jpg";
+ public static final String IMAGE_GIF = "image/gif";
+ public static final String IMAGE_WBMP = "image/vnd.wap.wbmp";
+ public static final String IMAGE_PNG = "image/png";
+ public static final String IMAGE_X_MS_BMP = "image/x-ms-bmp";
+
+ public static final String AUDIO_UNSPECIFIED = "audio/*";
+ public static final String AUDIO_AAC = "audio/aac";
+ public static final String AUDIO_AMR = "audio/amr";
+ public static final String AUDIO_IMELODY = "audio/imelody";
+ public static final String AUDIO_MID = "audio/mid";
+ public static final String AUDIO_MIDI = "audio/midi";
+ public static final String AUDIO_MP3 = "audio/mp3";
+ public static final String AUDIO_MPEG3 = "audio/mpeg3";
+ public static final String AUDIO_MPEG = "audio/mpeg";
+ public static final String AUDIO_MPG = "audio/mpg";
+ public static final String AUDIO_MP4 = "audio/mp4";
+ public static final String AUDIO_X_MID = "audio/x-mid";
+ public static final String AUDIO_X_MIDI = "audio/x-midi";
+ public static final String AUDIO_X_MP3 = "audio/x-mp3";
+ public static final String AUDIO_X_MPEG3 = "audio/x-mpeg3";
+ public static final String AUDIO_X_MPEG = "audio/x-mpeg";
+ public static final String AUDIO_X_MPG = "audio/x-mpg";
+ public static final String AUDIO_3GPP = "audio/3gpp";
+ public static final String AUDIO_X_WAV = "audio/x-wav";
+ public static final String AUDIO_OGG = "application/ogg";
+ public static final String AUDIO_OGG2 = "audio/ogg";
+
+ public static final String VIDEO_UNSPECIFIED = "video/*";
+ public static final String VIDEO_3GPP = "video/3gpp";
+ public static final String VIDEO_3G2 = "video/3gpp2";
+ public static final String VIDEO_H263 = "video/h263";
+ public static final String VIDEO_MP4 = "video/mp4";
+
+ public static final String APP_SMIL = "application/smil";
+ public static final String APP_WAP_XHTML = "application/vnd.wap.xhtml+xml";
+ public static final String APP_XHTML = "application/xhtml+xml";
+
+ public static final String APP_DRM_CONTENT = "application/vnd.oma.drm.content";
+ public static final String APP_DRM_MESSAGE = "application/vnd.oma.drm.message";
+
+ private static final ArrayList<String> sSupportedContentTypes = new ArrayList<String>();
+ private static final ArrayList<String> sSupportedImageTypes = new ArrayList<String>();
+ private static final ArrayList<String> sSupportedAudioTypes = new ArrayList<String>();
+ private static final ArrayList<String> sSupportedVideoTypes = new ArrayList<String>();
+
+ static {
+ sSupportedContentTypes.add(TEXT_PLAIN);
+ sSupportedContentTypes.add(TEXT_HTML);
+ sSupportedContentTypes.add(TEXT_VCALENDAR);
+ sSupportedContentTypes.add(TEXT_VCARD);
+
+ sSupportedContentTypes.add(IMAGE_JPEG);
+ sSupportedContentTypes.add(IMAGE_GIF);
+ sSupportedContentTypes.add(IMAGE_WBMP);
+ sSupportedContentTypes.add(IMAGE_PNG);
+ sSupportedContentTypes.add(IMAGE_JPG);
+ sSupportedContentTypes.add(IMAGE_X_MS_BMP);
+ //supportedContentTypes.add(IMAGE_SVG); not yet supported.
+
+ sSupportedContentTypes.add(AUDIO_AAC);
+ sSupportedContentTypes.add(AUDIO_AMR);
+ sSupportedContentTypes.add(AUDIO_IMELODY);
+ sSupportedContentTypes.add(AUDIO_MID);
+ sSupportedContentTypes.add(AUDIO_MIDI);
+ sSupportedContentTypes.add(AUDIO_MP3);
+ sSupportedContentTypes.add(AUDIO_MP4);
+ sSupportedContentTypes.add(AUDIO_MPEG3);
+ sSupportedContentTypes.add(AUDIO_MPEG);
+ sSupportedContentTypes.add(AUDIO_MPG);
+ sSupportedContentTypes.add(AUDIO_X_MID);
+ sSupportedContentTypes.add(AUDIO_X_MIDI);
+ sSupportedContentTypes.add(AUDIO_X_MP3);
+ sSupportedContentTypes.add(AUDIO_X_MPEG3);
+ sSupportedContentTypes.add(AUDIO_X_MPEG);
+ sSupportedContentTypes.add(AUDIO_X_MPG);
+ sSupportedContentTypes.add(AUDIO_X_WAV);
+ sSupportedContentTypes.add(AUDIO_3GPP);
+ sSupportedContentTypes.add(AUDIO_OGG);
+ sSupportedContentTypes.add(AUDIO_OGG2);
+
+ sSupportedContentTypes.add(VIDEO_3GPP);
+ sSupportedContentTypes.add(VIDEO_3G2);
+ sSupportedContentTypes.add(VIDEO_H263);
+ sSupportedContentTypes.add(VIDEO_MP4);
+
+ sSupportedContentTypes.add(APP_SMIL);
+ sSupportedContentTypes.add(APP_WAP_XHTML);
+ sSupportedContentTypes.add(APP_XHTML);
+
+ sSupportedContentTypes.add(APP_DRM_CONTENT);
+ sSupportedContentTypes.add(APP_DRM_MESSAGE);
+
+ // add supported image types
+ sSupportedImageTypes.add(IMAGE_JPEG);
+ sSupportedImageTypes.add(IMAGE_GIF);
+ sSupportedImageTypes.add(IMAGE_WBMP);
+ sSupportedImageTypes.add(IMAGE_PNG);
+ sSupportedImageTypes.add(IMAGE_JPG);
+ sSupportedImageTypes.add(IMAGE_X_MS_BMP);
+
+ // add supported audio types
+ sSupportedAudioTypes.add(AUDIO_AAC);
+ sSupportedAudioTypes.add(AUDIO_AMR);
+ sSupportedAudioTypes.add(AUDIO_IMELODY);
+ sSupportedAudioTypes.add(AUDIO_MID);
+ sSupportedAudioTypes.add(AUDIO_MIDI);
+ sSupportedAudioTypes.add(AUDIO_MP3);
+ sSupportedAudioTypes.add(AUDIO_MPEG3);
+ sSupportedAudioTypes.add(AUDIO_MPEG);
+ sSupportedAudioTypes.add(AUDIO_MPG);
+ sSupportedAudioTypes.add(AUDIO_MP4);
+ sSupportedAudioTypes.add(AUDIO_X_MID);
+ sSupportedAudioTypes.add(AUDIO_X_MIDI);
+ sSupportedAudioTypes.add(AUDIO_X_MP3);
+ sSupportedAudioTypes.add(AUDIO_X_MPEG3);
+ sSupportedAudioTypes.add(AUDIO_X_MPEG);
+ sSupportedAudioTypes.add(AUDIO_X_MPG);
+ sSupportedAudioTypes.add(AUDIO_X_WAV);
+ sSupportedAudioTypes.add(AUDIO_3GPP);
+ sSupportedAudioTypes.add(AUDIO_OGG);
+ sSupportedAudioTypes.add(AUDIO_OGG2);
+
+ // add supported video types
+ sSupportedVideoTypes.add(VIDEO_3GPP);
+ sSupportedVideoTypes.add(VIDEO_3G2);
+ sSupportedVideoTypes.add(VIDEO_H263);
+ sSupportedVideoTypes.add(VIDEO_MP4);
+ }
+
+ // This class should never be instantiated.
+ private ContentType() {
+ }
+
+ @UnsupportedAppUsage
+ public static boolean isSupportedType(String contentType) {
+ return (null != contentType) && sSupportedContentTypes.contains(contentType);
+ }
+
+ @UnsupportedAppUsage
+ public static boolean isSupportedImageType(String contentType) {
+ return isImageType(contentType) && isSupportedType(contentType);
+ }
+
+ @UnsupportedAppUsage
+ public static boolean isSupportedAudioType(String contentType) {
+ return isAudioType(contentType) && isSupportedType(contentType);
+ }
+
+ @UnsupportedAppUsage
+ public static boolean isSupportedVideoType(String contentType) {
+ return isVideoType(contentType) && isSupportedType(contentType);
+ }
+
+ @UnsupportedAppUsage
+ public static boolean isTextType(String contentType) {
+ return (null != contentType) && contentType.startsWith("text/");
+ }
+
+ @UnsupportedAppUsage
+ public static boolean isImageType(String contentType) {
+ return (null != contentType) && contentType.startsWith("image/");
+ }
+
+ @UnsupportedAppUsage
+ public static boolean isAudioType(String contentType) {
+ return (null != contentType) && contentType.startsWith("audio/");
+ }
+
+ @UnsupportedAppUsage
+ public static boolean isVideoType(String contentType) {
+ return (null != contentType) && contentType.startsWith("video/");
+ }
+
+ @UnsupportedAppUsage
+ public static boolean isDrmType(String contentType) {
+ return (null != contentType)
+ && (contentType.equals(APP_DRM_CONTENT)
+ || contentType.equals(APP_DRM_MESSAGE));
+ }
+
+ public static boolean isUnspecified(String contentType) {
+ return (null != contentType) && contentType.endsWith("*");
+ }
+
+ @UnsupportedAppUsage
+ @SuppressWarnings("unchecked")
+ public static ArrayList<String> getImageTypes() {
+ return (ArrayList<String>) sSupportedImageTypes.clone();
+ }
+
+ @UnsupportedAppUsage
+ @SuppressWarnings("unchecked")
+ public static ArrayList<String> getAudioTypes() {
+ return (ArrayList<String>) sSupportedAudioTypes.clone();
+ }
+
+ @UnsupportedAppUsage
+ @SuppressWarnings("unchecked")
+ public static ArrayList<String> getVideoTypes() {
+ return (ArrayList<String>) sSupportedVideoTypes.clone();
+ }
+
+ @SuppressWarnings("unchecked")
+ public static ArrayList<String> getSupportedTypes() {
+ return (ArrayList<String>) sSupportedContentTypes.clone();
+ }
+}
diff --git a/telephony/java/com/google/android/mms/InvalidHeaderValueException.java b/telephony/java/com/google/android/mms/InvalidHeaderValueException.java
new file mode 100644
index 0000000..2836c30
--- /dev/null
+++ b/telephony/java/com/google/android/mms/InvalidHeaderValueException.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+/**
+ * Thrown when an invalid header value was set.
+ */
+public class InvalidHeaderValueException extends MmsException {
+ private static final long serialVersionUID = -2053384496042052262L;
+
+ /**
+ * Constructs an InvalidHeaderValueException with no detailed message.
+ */
+ public InvalidHeaderValueException() {
+ super();
+ }
+
+ /**
+ * Constructs an InvalidHeaderValueException with the specified detailed message.
+ *
+ * @param message the detailed message.
+ */
+ @UnsupportedAppUsage
+ public InvalidHeaderValueException(String message) {
+ super(message);
+ }
+}
diff --git a/telephony/java/com/google/android/mms/MmsException.java b/telephony/java/com/google/android/mms/MmsException.java
new file mode 100644
index 0000000..5be33ed
--- /dev/null
+++ b/telephony/java/com/google/android/mms/MmsException.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+/**
+ * A generic exception that is thrown by the Mms client.
+ */
+public class MmsException extends Exception {
+ private static final long serialVersionUID = -7323249827281485390L;
+
+ /**
+ * Creates a new MmsException.
+ */
+ @UnsupportedAppUsage
+ public MmsException() {
+ super();
+ }
+
+ /**
+ * Creates a new MmsException with the specified detail message.
+ *
+ * @param message the detail message.
+ */
+ @UnsupportedAppUsage
+ public MmsException(String message) {
+ super(message);
+ }
+
+ /**
+ * Creates a new MmsException with the specified cause.
+ *
+ * @param cause the cause.
+ */
+ @UnsupportedAppUsage
+ public MmsException(Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * Creates a new MmsException with the specified detail message and cause.
+ *
+ * @param message the detail message.
+ * @param cause the cause.
+ */
+ @UnsupportedAppUsage
+ public MmsException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/telephony/java/com/google/android/mms/package.html b/telephony/java/com/google/android/mms/package.html
new file mode 100755
index 0000000..c9f96a6
--- /dev/null
+++ b/telephony/java/com/google/android/mms/package.html
@@ -0,0 +1,5 @@
+<body>
+
+{@hide}
+
+</body>
diff --git a/telephony/java/com/google/android/mms/pdu/AcknowledgeInd.java b/telephony/java/com/google/android/mms/pdu/AcknowledgeInd.java
new file mode 100644
index 0000000..ae447d7
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/AcknowledgeInd.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+/**
+ * M-Acknowledge.ind PDU.
+ */
+public class AcknowledgeInd extends GenericPdu {
+ /**
+ * Constructor, used when composing a M-Acknowledge.ind pdu.
+ *
+ * @param mmsVersion current viersion of mms
+ * @param transactionId the transaction-id value
+ * @throws InvalidHeaderValueException if parameters are invalid.
+ * NullPointerException if transactionId is null.
+ */
+ @UnsupportedAppUsage
+ public AcknowledgeInd(int mmsVersion, byte[] transactionId)
+ throws InvalidHeaderValueException {
+ super();
+
+ setMessageType(PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND);
+ setMmsVersion(mmsVersion);
+ setTransactionId(transactionId);
+ }
+
+ /**
+ * Constructor with given headers.
+ *
+ * @param headers Headers for this PDU.
+ */
+ @UnsupportedAppUsage
+ AcknowledgeInd(PduHeaders headers) {
+ super(headers);
+ }
+
+ /**
+ * Get X-Mms-Report-Allowed field value.
+ *
+ * @return the X-Mms-Report-Allowed value
+ */
+ public int getReportAllowed() {
+ return mPduHeaders.getOctet(PduHeaders.REPORT_ALLOWED);
+ }
+
+ /**
+ * Set X-Mms-Report-Allowed field value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ @UnsupportedAppUsage
+ public void setReportAllowed(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.REPORT_ALLOWED);
+ }
+
+ /**
+ * Get X-Mms-Transaction-Id field value.
+ *
+ * @return the X-Mms-Report-Allowed value
+ */
+ public byte[] getTransactionId() {
+ return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+ }
+
+ /**
+ * Set X-Mms-Transaction-Id field value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ @UnsupportedAppUsage
+ public void setTransactionId(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+ }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/Base64.java b/telephony/java/com/google/android/mms/pdu/Base64.java
new file mode 100644
index 0000000..483fa7f
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/Base64.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+public class Base64 {
+ /**
+ * Used to get the number of Quadruples.
+ */
+ static final int FOURBYTE = 4;
+
+ /**
+ * Byte used to pad output.
+ */
+ static final byte PAD = (byte) '=';
+
+ /**
+ * The base length.
+ */
+ static final int BASELENGTH = 255;
+
+ // Create arrays to hold the base64 characters
+ private static byte[] base64Alphabet = new byte[BASELENGTH];
+
+ // Populating the character arrays
+ static {
+ for (int i = 0; i < BASELENGTH; i++) {
+ base64Alphabet[i] = (byte) -1;
+ }
+ for (int i = 'Z'; i >= 'A'; i--) {
+ base64Alphabet[i] = (byte) (i - 'A');
+ }
+ for (int i = 'z'; i >= 'a'; i--) {
+ base64Alphabet[i] = (byte) (i - 'a' + 26);
+ }
+ for (int i = '9'; i >= '0'; i--) {
+ base64Alphabet[i] = (byte) (i - '0' + 52);
+ }
+
+ base64Alphabet['+'] = 62;
+ base64Alphabet['/'] = 63;
+ }
+
+ /**
+ * Decodes Base64 data into octects
+ *
+ * @param base64Data Byte array containing Base64 data
+ * @return Array containing decoded data.
+ */
+ @UnsupportedAppUsage
+ public static byte[] decodeBase64(byte[] base64Data) {
+ // RFC 2045 requires that we discard ALL non-Base64 characters
+ base64Data = discardNonBase64(base64Data);
+
+ // handle the edge case, so we don't have to worry about it later
+ if (base64Data.length == 0) {
+ return new byte[0];
+ }
+
+ int numberQuadruple = base64Data.length / FOURBYTE;
+ byte decodedData[] = null;
+ byte b1 = 0, b2 = 0, b3 = 0, b4 = 0, marker0 = 0, marker1 = 0;
+
+ // Throw away anything not in base64Data
+
+ int encodedIndex = 0;
+ int dataIndex = 0;
+ {
+ // this sizes the output array properly - rlw
+ int lastData = base64Data.length;
+ // ignore the '=' padding
+ while (base64Data[lastData - 1] == PAD) {
+ if (--lastData == 0) {
+ return new byte[0];
+ }
+ }
+ decodedData = new byte[lastData - numberQuadruple];
+ }
+
+ for (int i = 0; i < numberQuadruple; i++) {
+ dataIndex = i * 4;
+ marker0 = base64Data[dataIndex + 2];
+ marker1 = base64Data[dataIndex + 3];
+
+ b1 = base64Alphabet[base64Data[dataIndex]];
+ b2 = base64Alphabet[base64Data[dataIndex + 1]];
+
+ if (marker0 != PAD && marker1 != PAD) {
+ //No PAD e.g 3cQl
+ b3 = base64Alphabet[marker0];
+ b4 = base64Alphabet[marker1];
+
+ decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+ decodedData[encodedIndex + 1] =
+ (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+ decodedData[encodedIndex + 2] = (byte) (b3 << 6 | b4);
+ } else if (marker0 == PAD) {
+ //Two PAD e.g. 3c[Pad][Pad]
+ decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+ } else if (marker1 == PAD) {
+ //One PAD e.g. 3cQ[Pad]
+ b3 = base64Alphabet[marker0];
+
+ decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+ decodedData[encodedIndex + 1] =
+ (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+ }
+ encodedIndex += 3;
+ }
+ return decodedData;
+ }
+
+ /**
+ * Check octect whether it is a base64 encoding.
+ *
+ * @param octect to be checked byte
+ * @return ture if it is base64 encoding, false otherwise.
+ */
+ private static boolean isBase64(byte octect) {
+ if (octect == PAD) {
+ return true;
+ } else if (base64Alphabet[octect] == -1) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Discards any characters outside of the base64 alphabet, per
+ * the requirements on page 25 of RFC 2045 - "Any characters
+ * outside of the base64 alphabet are to be ignored in base64
+ * encoded data."
+ *
+ * @param data The base-64 encoded data to groom
+ * @return The data, less non-base64 characters (see RFC 2045).
+ */
+ static byte[] discardNonBase64(byte[] data) {
+ byte groomedData[] = new byte[data.length];
+ int bytesCopied = 0;
+
+ for (int i = 0; i < data.length; i++) {
+ if (isBase64(data[i])) {
+ groomedData[bytesCopied++] = data[i];
+ }
+ }
+
+ byte packedData[] = new byte[bytesCopied];
+
+ System.arraycopy(groomedData, 0, packedData, 0, bytesCopied);
+
+ return packedData;
+ }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/CharacterSets.java b/telephony/java/com/google/android/mms/pdu/CharacterSets.java
new file mode 100644
index 0000000..27da35e
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/CharacterSets.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+
+public class CharacterSets {
+ /**
+ * IANA assigned MIB enum numbers.
+ *
+ * From wap-230-wsp-20010705-a.pdf
+ * Any-charset = <Octet 128>
+ * Equivalent to the special RFC2616 charset value "*"
+ */
+ public static final int ANY_CHARSET = 0x00;
+ public static final int US_ASCII = 0x03;
+ public static final int ISO_8859_1 = 0x04;
+ public static final int ISO_8859_2 = 0x05;
+ public static final int ISO_8859_3 = 0x06;
+ public static final int ISO_8859_4 = 0x07;
+ public static final int ISO_8859_5 = 0x08;
+ public static final int ISO_8859_6 = 0x09;
+ public static final int ISO_8859_7 = 0x0A;
+ public static final int ISO_8859_8 = 0x0B;
+ public static final int ISO_8859_9 = 0x0C;
+ public static final int SHIFT_JIS = 0x11;
+ public static final int UTF_8 = 0x6A;
+ public static final int BIG5 = 0x07EA;
+ public static final int UCS2 = 0x03E8;
+ public static final int UTF_16 = 0x03F7;
+
+ /**
+ * If the encoding of given data is unsupported, use UTF_8 to decode it.
+ */
+ public static final int DEFAULT_CHARSET = UTF_8;
+
+ /**
+ * Array of MIB enum numbers.
+ */
+ private static final int[] MIBENUM_NUMBERS = {
+ ANY_CHARSET,
+ US_ASCII,
+ ISO_8859_1,
+ ISO_8859_2,
+ ISO_8859_3,
+ ISO_8859_4,
+ ISO_8859_5,
+ ISO_8859_6,
+ ISO_8859_7,
+ ISO_8859_8,
+ ISO_8859_9,
+ SHIFT_JIS,
+ UTF_8,
+ BIG5,
+ UCS2,
+ UTF_16,
+ };
+
+ /**
+ * The Well-known-charset Mime name.
+ */
+ public static final String MIMENAME_ANY_CHARSET = "*";
+ public static final String MIMENAME_US_ASCII = "us-ascii";
+ public static final String MIMENAME_ISO_8859_1 = "iso-8859-1";
+ public static final String MIMENAME_ISO_8859_2 = "iso-8859-2";
+ public static final String MIMENAME_ISO_8859_3 = "iso-8859-3";
+ public static final String MIMENAME_ISO_8859_4 = "iso-8859-4";
+ public static final String MIMENAME_ISO_8859_5 = "iso-8859-5";
+ public static final String MIMENAME_ISO_8859_6 = "iso-8859-6";
+ public static final String MIMENAME_ISO_8859_7 = "iso-8859-7";
+ public static final String MIMENAME_ISO_8859_8 = "iso-8859-8";
+ public static final String MIMENAME_ISO_8859_9 = "iso-8859-9";
+ public static final String MIMENAME_SHIFT_JIS = "shift_JIS";
+ public static final String MIMENAME_UTF_8 = "utf-8";
+ public static final String MIMENAME_BIG5 = "big5";
+ public static final String MIMENAME_UCS2 = "iso-10646-ucs-2";
+ public static final String MIMENAME_UTF_16 = "utf-16";
+
+ public static final String DEFAULT_CHARSET_NAME = MIMENAME_UTF_8;
+
+ /**
+ * Array of the names of character sets.
+ */
+ private static final String[] MIME_NAMES = {
+ MIMENAME_ANY_CHARSET,
+ MIMENAME_US_ASCII,
+ MIMENAME_ISO_8859_1,
+ MIMENAME_ISO_8859_2,
+ MIMENAME_ISO_8859_3,
+ MIMENAME_ISO_8859_4,
+ MIMENAME_ISO_8859_5,
+ MIMENAME_ISO_8859_6,
+ MIMENAME_ISO_8859_7,
+ MIMENAME_ISO_8859_8,
+ MIMENAME_ISO_8859_9,
+ MIMENAME_SHIFT_JIS,
+ MIMENAME_UTF_8,
+ MIMENAME_BIG5,
+ MIMENAME_UCS2,
+ MIMENAME_UTF_16,
+ };
+
+ private static final HashMap<Integer, String> MIBENUM_TO_NAME_MAP;
+ private static final HashMap<String, Integer> NAME_TO_MIBENUM_MAP;
+
+ static {
+ // Create the HashMaps.
+ MIBENUM_TO_NAME_MAP = new HashMap<Integer, String>();
+ NAME_TO_MIBENUM_MAP = new HashMap<String, Integer>();
+ assert(MIBENUM_NUMBERS.length == MIME_NAMES.length);
+ int count = MIBENUM_NUMBERS.length - 1;
+ for(int i = 0; i <= count; i++) {
+ MIBENUM_TO_NAME_MAP.put(MIBENUM_NUMBERS[i], MIME_NAMES[i]);
+ NAME_TO_MIBENUM_MAP.put(MIME_NAMES[i], MIBENUM_NUMBERS[i]);
+ }
+ }
+
+ private CharacterSets() {} // Non-instantiatable
+
+ /**
+ * Map an MIBEnum number to the name of the charset which this number
+ * is assigned to by IANA.
+ *
+ * @param mibEnumValue An IANA assigned MIBEnum number.
+ * @return The name string of the charset.
+ * @throws UnsupportedEncodingException
+ */
+ @UnsupportedAppUsage
+ public static String getMimeName(int mibEnumValue)
+ throws UnsupportedEncodingException {
+ String name = MIBENUM_TO_NAME_MAP.get(mibEnumValue);
+ if (name == null) {
+ throw new UnsupportedEncodingException();
+ }
+ return name;
+ }
+
+ /**
+ * Map a well-known charset name to its assigned MIBEnum number.
+ *
+ * @param mimeName The charset name.
+ * @return The MIBEnum number assigned by IANA for this charset.
+ * @throws UnsupportedEncodingException
+ */
+ @UnsupportedAppUsage
+ public static int getMibEnumValue(String mimeName)
+ throws UnsupportedEncodingException {
+ if(null == mimeName) {
+ return -1;
+ }
+
+ Integer mibEnumValue = NAME_TO_MIBENUM_MAP.get(mimeName);
+ if (mibEnumValue == null) {
+ throw new UnsupportedEncodingException();
+ }
+ return mibEnumValue;
+ }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/DeliveryInd.java b/telephony/java/com/google/android/mms/pdu/DeliveryInd.java
new file mode 100644
index 0000000..7093ac6
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/DeliveryInd.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+/**
+ * M-Delivery.Ind Pdu.
+ */
+public class DeliveryInd extends GenericPdu {
+ /**
+ * Empty constructor.
+ * Since the Pdu corresponding to this class is constructed
+ * by the Proxy-Relay server, this class is only instantiated
+ * by the Pdu Parser.
+ *
+ * @throws InvalidHeaderValueException if error occurs.
+ */
+ public DeliveryInd() throws InvalidHeaderValueException {
+ super();
+ setMessageType(PduHeaders.MESSAGE_TYPE_DELIVERY_IND);
+ }
+
+ /**
+ * Constructor with given headers.
+ *
+ * @param headers Headers for this PDU.
+ */
+ @UnsupportedAppUsage
+ DeliveryInd(PduHeaders headers) {
+ super(headers);
+ }
+
+ /**
+ * Get Date value.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public long getDate() {
+ return mPduHeaders.getLongInteger(PduHeaders.DATE);
+ }
+
+ /**
+ * Set Date value.
+ *
+ * @param value the value
+ */
+ public void setDate(long value) {
+ mPduHeaders.setLongInteger(value, PduHeaders.DATE);
+ }
+
+ /**
+ * Get Message-ID value.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public byte[] getMessageId() {
+ return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
+ }
+
+ /**
+ * Set Message-ID value.
+ *
+ * @param value the value, should not be null
+ * @throws NullPointerException if the value is null.
+ */
+ public void setMessageId(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
+ }
+
+ /**
+ * Get Status value.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public int getStatus() {
+ return mPduHeaders.getOctet(PduHeaders.STATUS);
+ }
+
+ /**
+ * Set Status value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ public void setStatus(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.STATUS);
+ }
+
+ /**
+ * Get To value.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public EncodedStringValue[] getTo() {
+ return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
+ }
+
+ /**
+ * set To value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setTo(EncodedStringValue[] value) {
+ mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
+ }
+
+ /*
+ * Optional, not supported header fields:
+ *
+ * public byte[] getApplicId() {return null;}
+ * public void setApplicId(byte[] value) {}
+ *
+ * public byte[] getAuxApplicId() {return null;}
+ * public void getAuxApplicId(byte[] value) {}
+ *
+ * public byte[] getReplyApplicId() {return 0x00;}
+ * public void setReplyApplicId(byte[] value) {}
+ *
+ * public EncodedStringValue getStatusText() {return null;}
+ * public void setStatusText(EncodedStringValue value) {}
+ */
+}
diff --git a/telephony/java/com/google/android/mms/pdu/EncodedStringValue.java b/telephony/java/com/google/android/mms/pdu/EncodedStringValue.java
new file mode 100644
index 0000000..4166275
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/EncodedStringValue.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-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.google.android.mms.pdu;
+
+import android.util.Log;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+
+/**
+ * Encoded-string-value = Text-string | Value-length Char-set Text-string
+ */
+public class EncodedStringValue implements Cloneable {
+ private static final String TAG = "EncodedStringValue";
+ private static final boolean DEBUG = false;
+ private static final boolean LOCAL_LOGV = false;
+
+ /**
+ * The Char-set value.
+ */
+ private int mCharacterSet;
+
+ /**
+ * The Text-string value.
+ */
+ private byte[] mData;
+
+ /**
+ * Constructor.
+ *
+ * @param charset the Char-set value
+ * @param data the Text-string value
+ * @throws NullPointerException if Text-string value is null.
+ */
+ @UnsupportedAppUsage
+ public EncodedStringValue(int charset, byte[] data) {
+ // TODO: CharSet needs to be validated against MIBEnum.
+ if(null == data) {
+ throw new NullPointerException("EncodedStringValue: Text-string is null.");
+ }
+
+ mCharacterSet = charset;
+ mData = new byte[data.length];
+ System.arraycopy(data, 0, mData, 0, data.length);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param data the Text-string value
+ * @throws NullPointerException if Text-string value is null.
+ */
+ @UnsupportedAppUsage
+ public EncodedStringValue(byte[] data) {
+ this(CharacterSets.DEFAULT_CHARSET, data);
+ }
+
+ @UnsupportedAppUsage
+ public EncodedStringValue(String data) {
+ try {
+ mData = data.getBytes(CharacterSets.DEFAULT_CHARSET_NAME);
+ mCharacterSet = CharacterSets.DEFAULT_CHARSET;
+ } catch (UnsupportedEncodingException e) {
+ Log.e(TAG, "Default encoding must be supported.", e);
+ }
+ }
+
+ /**
+ * Get Char-set value.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public int getCharacterSet() {
+ return mCharacterSet;
+ }
+
+ /**
+ * Set Char-set value.
+ *
+ * @param charset the Char-set value
+ */
+ @UnsupportedAppUsage
+ public void setCharacterSet(int charset) {
+ // TODO: CharSet needs to be validated against MIBEnum.
+ mCharacterSet = charset;
+ }
+
+ /**
+ * Get Text-string value.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public byte[] getTextString() {
+ byte[] byteArray = new byte[mData.length];
+
+ System.arraycopy(mData, 0, byteArray, 0, mData.length);
+ return byteArray;
+ }
+
+ /**
+ * Set Text-string value.
+ *
+ * @param textString the Text-string value
+ * @throws NullPointerException if Text-string value is null.
+ */
+ @UnsupportedAppUsage
+ public void setTextString(byte[] textString) {
+ if(null == textString) {
+ throw new NullPointerException("EncodedStringValue: Text-string is null.");
+ }
+
+ mData = new byte[textString.length];
+ System.arraycopy(textString, 0, mData, 0, textString.length);
+ }
+
+ /**
+ * Convert this object to a {@link java.lang.String}. If the encoding of
+ * the EncodedStringValue is null or unsupported, it will be
+ * treated as iso-8859-1 encoding.
+ *
+ * @return The decoded String.
+ */
+ @UnsupportedAppUsage
+ public String getString() {
+ if (CharacterSets.ANY_CHARSET == mCharacterSet) {
+ return new String(mData); // system default encoding.
+ } else {
+ try {
+ String name = CharacterSets.getMimeName(mCharacterSet);
+ return new String(mData, name);
+ } catch (UnsupportedEncodingException e) {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, e.getMessage(), e);
+ }
+ try {
+ return new String(mData, CharacterSets.MIMENAME_ISO_8859_1);
+ } catch (UnsupportedEncodingException e2) {
+ return new String(mData); // system default encoding.
+ }
+ }
+ }
+ }
+
+ /**
+ * Append to Text-string.
+ *
+ * @param textString the textString to append
+ * @throws NullPointerException if the text String is null
+ * or an IOException occurred.
+ */
+ @UnsupportedAppUsage
+ public void appendTextString(byte[] textString) {
+ if(null == textString) {
+ throw new NullPointerException("Text-string is null.");
+ }
+
+ if(null == mData) {
+ mData = new byte[textString.length];
+ System.arraycopy(textString, 0, mData, 0, textString.length);
+ } else {
+ ByteArrayOutputStream newTextString = new ByteArrayOutputStream();
+ try {
+ newTextString.write(mData);
+ newTextString.write(textString);
+ } catch (IOException e) {
+ e.printStackTrace();
+ throw new NullPointerException(
+ "appendTextString: failed when write a new Text-string");
+ }
+
+ mData = newTextString.toByteArray();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#clone()
+ */
+ @Override
+ public Object clone() throws CloneNotSupportedException {
+ super.clone();
+ int len = mData.length;
+ byte[] dstBytes = new byte[len];
+ System.arraycopy(mData, 0, dstBytes, 0, len);
+
+ try {
+ return new EncodedStringValue(mCharacterSet, dstBytes);
+ } catch (Exception e) {
+ Log.e(TAG, "failed to clone an EncodedStringValue: " + this);
+ e.printStackTrace();
+ throw new CloneNotSupportedException(e.getMessage());
+ }
+ }
+
+ /**
+ * Split this encoded string around matches of the given pattern.
+ *
+ * @param pattern the delimiting pattern
+ * @return the array of encoded strings computed by splitting this encoded
+ * string around matches of the given pattern
+ */
+ public EncodedStringValue[] split(String pattern) {
+ String[] temp = getString().split(pattern);
+ EncodedStringValue[] ret = new EncodedStringValue[temp.length];
+ for (int i = 0; i < ret.length; ++i) {
+ try {
+ ret[i] = new EncodedStringValue(mCharacterSet,
+ temp[i].getBytes());
+ } catch (NullPointerException e) {
+ // Can't arrive here
+ return null;
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * Extract an EncodedStringValue[] from a given String.
+ */
+ @UnsupportedAppUsage
+ public static EncodedStringValue[] extract(String src) {
+ String[] values = src.split(";");
+
+ ArrayList<EncodedStringValue> list = new ArrayList<EncodedStringValue>();
+ for (int i = 0; i < values.length; i++) {
+ if (values[i].length() > 0) {
+ list.add(new EncodedStringValue(values[i]));
+ }
+ }
+
+ int len = list.size();
+ if (len > 0) {
+ return list.toArray(new EncodedStringValue[len]);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Concatenate an EncodedStringValue[] into a single String.
+ */
+ @UnsupportedAppUsage
+ public static String concat(EncodedStringValue[] addr) {
+ StringBuilder sb = new StringBuilder();
+ int maxIndex = addr.length - 1;
+ for (int i = 0; i <= maxIndex; i++) {
+ sb.append(addr[i].getString());
+ if (i < maxIndex) {
+ sb.append(";");
+ }
+ }
+
+ return sb.toString();
+ }
+
+ @UnsupportedAppUsage
+ public static EncodedStringValue copy(EncodedStringValue value) {
+ if (value == null) {
+ return null;
+ }
+
+ return new EncodedStringValue(value.mCharacterSet, value.mData);
+ }
+
+ @UnsupportedAppUsage
+ public static EncodedStringValue[] encodeStrings(String[] array) {
+ int count = array.length;
+ if (count > 0) {
+ EncodedStringValue[] encodedArray = new EncodedStringValue[count];
+ for (int i = 0; i < count; i++) {
+ encodedArray[i] = new EncodedStringValue(array[i]);
+ }
+ return encodedArray;
+ }
+ return null;
+ }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/GenericPdu.java b/telephony/java/com/google/android/mms/pdu/GenericPdu.java
new file mode 100644
index 0000000..ebf16ac
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/GenericPdu.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+public class GenericPdu {
+ /**
+ * The headers of pdu.
+ */
+ @UnsupportedAppUsage
+ PduHeaders mPduHeaders = null;
+
+ /**
+ * Constructor.
+ */
+ @UnsupportedAppUsage
+ public GenericPdu() {
+ mPduHeaders = new PduHeaders();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param headers Headers for this PDU.
+ */
+ GenericPdu(PduHeaders headers) {
+ mPduHeaders = headers;
+ }
+
+ /**
+ * Get the headers of this PDU.
+ *
+ * @return A PduHeaders of this PDU.
+ */
+ @UnsupportedAppUsage
+ PduHeaders getPduHeaders() {
+ return mPduHeaders;
+ }
+
+ /**
+ * Get X-Mms-Message-Type field value.
+ *
+ * @return the X-Mms-Report-Allowed value
+ */
+ @UnsupportedAppUsage
+ public int getMessageType() {
+ return mPduHeaders.getOctet(PduHeaders.MESSAGE_TYPE);
+ }
+
+ /**
+ * Set X-Mms-Message-Type field value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ * RuntimeException if field's value is not Octet.
+ */
+ @UnsupportedAppUsage
+ public void setMessageType(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.MESSAGE_TYPE);
+ }
+
+ /**
+ * Get X-Mms-MMS-Version field value.
+ *
+ * @return the X-Mms-MMS-Version value
+ */
+ public int getMmsVersion() {
+ return mPduHeaders.getOctet(PduHeaders.MMS_VERSION);
+ }
+
+ /**
+ * Set X-Mms-MMS-Version field value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ * RuntimeException if field's value is not Octet.
+ */
+ public void setMmsVersion(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.MMS_VERSION);
+ }
+
+ /**
+ * Get From value.
+ * From-value = Value-length
+ * (Address-present-token Encoded-string-value | Insert-address-token)
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public EncodedStringValue getFrom() {
+ return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
+ }
+
+ /**
+ * Set From value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ @UnsupportedAppUsage
+ public void setFrom(EncodedStringValue value) {
+ mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
+ }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/MultimediaMessagePdu.java b/telephony/java/com/google/android/mms/pdu/MultimediaMessagePdu.java
new file mode 100644
index 0000000..e108f76
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/MultimediaMessagePdu.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+/**
+ * Multimedia message PDU.
+ */
+public class MultimediaMessagePdu extends GenericPdu{
+ /**
+ * The body.
+ */
+ private PduBody mMessageBody;
+
+ /**
+ * Constructor.
+ */
+ @UnsupportedAppUsage
+ public MultimediaMessagePdu() {
+ super();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param header the header of this PDU
+ * @param body the body of this PDU
+ */
+ @UnsupportedAppUsage
+ public MultimediaMessagePdu(PduHeaders header, PduBody body) {
+ super(header);
+ mMessageBody = body;
+ }
+
+ /**
+ * Constructor with given headers.
+ *
+ * @param headers Headers for this PDU.
+ */
+ MultimediaMessagePdu(PduHeaders headers) {
+ super(headers);
+ }
+
+ /**
+ * Get body of the PDU.
+ *
+ * @return the body
+ */
+ @UnsupportedAppUsage
+ public PduBody getBody() {
+ return mMessageBody;
+ }
+
+ /**
+ * Set body of the PDU.
+ *
+ * @param body the body
+ */
+ @UnsupportedAppUsage
+ public void setBody(PduBody body) {
+ mMessageBody = body;
+ }
+
+ /**
+ * Get subject.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public EncodedStringValue getSubject() {
+ return mPduHeaders.getEncodedStringValue(PduHeaders.SUBJECT);
+ }
+
+ /**
+ * Set subject.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ @UnsupportedAppUsage
+ public void setSubject(EncodedStringValue value) {
+ mPduHeaders.setEncodedStringValue(value, PduHeaders.SUBJECT);
+ }
+
+ /**
+ * Get To value.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public EncodedStringValue[] getTo() {
+ return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
+ }
+
+ /**
+ * Add a "To" value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ @UnsupportedAppUsage
+ public void addTo(EncodedStringValue value) {
+ mPduHeaders.appendEncodedStringValue(value, PduHeaders.TO);
+ }
+
+ /**
+ * Get X-Mms-Priority value.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public int getPriority() {
+ return mPduHeaders.getOctet(PduHeaders.PRIORITY);
+ }
+
+ /**
+ * Set X-Mms-Priority value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ @UnsupportedAppUsage
+ public void setPriority(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.PRIORITY);
+ }
+
+ /**
+ * Get Date value.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public long getDate() {
+ return mPduHeaders.getLongInteger(PduHeaders.DATE);
+ }
+
+ /**
+ * Set Date value in seconds.
+ *
+ * @param value the value
+ */
+ @UnsupportedAppUsage
+ public void setDate(long value) {
+ mPduHeaders.setLongInteger(value, PduHeaders.DATE);
+ }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/NotificationInd.java b/telephony/java/com/google/android/mms/pdu/NotificationInd.java
new file mode 100644
index 0000000..b561bd4
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/NotificationInd.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+/**
+ * M-Notification.ind PDU.
+ */
+public class NotificationInd extends GenericPdu {
+ /**
+ * Empty constructor.
+ * Since the Pdu corresponding to this class is constructed
+ * by the Proxy-Relay server, this class is only instantiated
+ * by the Pdu Parser.
+ *
+ * @throws InvalidHeaderValueException if error occurs.
+ * RuntimeException if an undeclared error occurs.
+ */
+ @UnsupportedAppUsage
+ public NotificationInd() throws InvalidHeaderValueException {
+ super();
+ setMessageType(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND);
+ }
+
+ /**
+ * Constructor with given headers.
+ *
+ * @param headers Headers for this PDU.
+ */
+ @UnsupportedAppUsage
+ NotificationInd(PduHeaders headers) {
+ super(headers);
+ }
+
+ /**
+ * Get X-Mms-Content-Class Value.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public int getContentClass() {
+ return mPduHeaders.getOctet(PduHeaders.CONTENT_CLASS);
+ }
+
+ /**
+ * Set X-Mms-Content-Class Value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ * RuntimeException if an undeclared error occurs.
+ */
+ @UnsupportedAppUsage
+ public void setContentClass(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.CONTENT_CLASS);
+ }
+
+ /**
+ * Get X-Mms-Content-Location value.
+ * When used in a PDU other than M-Mbox-Delete.conf and M-Delete.conf:
+ * Content-location-value = Uri-value
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public byte[] getContentLocation() {
+ return mPduHeaders.getTextString(PduHeaders.CONTENT_LOCATION);
+ }
+
+ /**
+ * Set X-Mms-Content-Location value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ * RuntimeException if an undeclared error occurs.
+ */
+ @UnsupportedAppUsage
+ public void setContentLocation(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.CONTENT_LOCATION);
+ }
+
+ /**
+ * Get X-Mms-Expiry value.
+ *
+ * Expiry-value = Value-length
+ * (Absolute-token Date-value | Relative-token Delta-seconds-value)
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public long getExpiry() {
+ return mPduHeaders.getLongInteger(PduHeaders.EXPIRY);
+ }
+
+ /**
+ * Set X-Mms-Expiry value.
+ *
+ * @param value the value
+ * @throws RuntimeException if an undeclared error occurs.
+ */
+ @UnsupportedAppUsage
+ public void setExpiry(long value) {
+ mPduHeaders.setLongInteger(value, PduHeaders.EXPIRY);
+ }
+
+ /**
+ * Get From value.
+ * From-value = Value-length
+ * (Address-present-token Encoded-string-value | Insert-address-token)
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public EncodedStringValue getFrom() {
+ return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
+ }
+
+ /**
+ * Set From value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ * RuntimeException if an undeclared error occurs.
+ */
+ @UnsupportedAppUsage
+ public void setFrom(EncodedStringValue value) {
+ mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
+ }
+
+ /**
+ * Get X-Mms-Message-Class value.
+ * Message-class-value = Class-identifier | Token-text
+ * Class-identifier = Personal | Advertisement | Informational | Auto
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public byte[] getMessageClass() {
+ return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS);
+ }
+
+ /**
+ * Set X-Mms-Message-Class value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ * RuntimeException if an undeclared error occurs.
+ */
+ @UnsupportedAppUsage
+ public void setMessageClass(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS);
+ }
+
+ /**
+ * Get X-Mms-Message-Size value.
+ * Message-size-value = Long-integer
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public long getMessageSize() {
+ return mPduHeaders.getLongInteger(PduHeaders.MESSAGE_SIZE);
+ }
+
+ /**
+ * Set X-Mms-Message-Size value.
+ *
+ * @param value the value
+ * @throws RuntimeException if an undeclared error occurs.
+ */
+ @UnsupportedAppUsage
+ public void setMessageSize(long value) {
+ mPduHeaders.setLongInteger(value, PduHeaders.MESSAGE_SIZE);
+ }
+
+ /**
+ * Get subject.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public EncodedStringValue getSubject() {
+ return mPduHeaders.getEncodedStringValue(PduHeaders.SUBJECT);
+ }
+
+ /**
+ * Set subject.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ * RuntimeException if an undeclared error occurs.
+ */
+ @UnsupportedAppUsage
+ public void setSubject(EncodedStringValue value) {
+ mPduHeaders.setEncodedStringValue(value, PduHeaders.SUBJECT);
+ }
+
+ /**
+ * Get X-Mms-Transaction-Id.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public byte[] getTransactionId() {
+ return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+ }
+
+ /**
+ * Set X-Mms-Transaction-Id.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ * RuntimeException if an undeclared error occurs.
+ */
+ @UnsupportedAppUsage
+ public void setTransactionId(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+ }
+
+ /**
+ * Get X-Mms-Delivery-Report Value.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public int getDeliveryReport() {
+ return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT);
+ }
+
+ /**
+ * Set X-Mms-Delivery-Report Value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ * RuntimeException if an undeclared error occurs.
+ */
+ @UnsupportedAppUsage
+ public void setDeliveryReport(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT);
+ }
+
+ /*
+ * Optional, not supported header fields:
+ *
+ * public byte[] getApplicId() {return null;}
+ * public void setApplicId(byte[] value) {}
+ *
+ * public byte[] getAuxApplicId() {return null;}
+ * public void getAuxApplicId(byte[] value) {}
+ *
+ * public byte getDrmContent() {return 0x00;}
+ * public void setDrmContent(byte value) {}
+ *
+ * public byte getDistributionIndicator() {return 0x00;}
+ * public void setDistributionIndicator(byte value) {}
+ *
+ * public ElementDescriptorValue getElementDescriptor() {return null;}
+ * public void getElementDescriptor(ElementDescriptorValue value) {}
+ *
+ * public byte getPriority() {return 0x00;}
+ * public void setPriority(byte value) {}
+ *
+ * public byte getRecommendedRetrievalMode() {return 0x00;}
+ * public void setRecommendedRetrievalMode(byte value) {}
+ *
+ * public byte getRecommendedRetrievalModeText() {return 0x00;}
+ * public void setRecommendedRetrievalModeText(byte value) {}
+ *
+ * public byte[] getReplaceId() {return 0x00;}
+ * public void setReplaceId(byte[] value) {}
+ *
+ * public byte[] getReplyApplicId() {return 0x00;}
+ * public void setReplyApplicId(byte[] value) {}
+ *
+ * public byte getReplyCharging() {return 0x00;}
+ * public void setReplyCharging(byte value) {}
+ *
+ * public byte getReplyChargingDeadline() {return 0x00;}
+ * public void setReplyChargingDeadline(byte value) {}
+ *
+ * public byte[] getReplyChargingId() {return 0x00;}
+ * public void setReplyChargingId(byte[] value) {}
+ *
+ * public long getReplyChargingSize() {return 0;}
+ * public void setReplyChargingSize(long value) {}
+ *
+ * public byte getStored() {return 0x00;}
+ * public void setStored(byte value) {}
+ */
+}
diff --git a/telephony/java/com/google/android/mms/pdu/NotifyRespInd.java b/telephony/java/com/google/android/mms/pdu/NotifyRespInd.java
new file mode 100644
index 0000000..3c70f86
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/NotifyRespInd.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+/**
+ * M-NofifyResp.ind PDU.
+ */
+public class NotifyRespInd extends GenericPdu {
+ /**
+ * Constructor, used when composing a M-NotifyResp.ind pdu.
+ *
+ * @param mmsVersion current version of mms
+ * @param transactionId the transaction-id value
+ * @param status the status value
+ * @throws InvalidHeaderValueException if parameters are invalid.
+ * NullPointerException if transactionId is null.
+ * RuntimeException if an undeclared error occurs.
+ */
+ @UnsupportedAppUsage
+ public NotifyRespInd(int mmsVersion,
+ byte[] transactionId,
+ int status) throws InvalidHeaderValueException {
+ super();
+ setMessageType(PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND);
+ setMmsVersion(mmsVersion);
+ setTransactionId(transactionId);
+ setStatus(status);
+ }
+
+ /**
+ * Constructor with given headers.
+ *
+ * @param headers Headers for this PDU.
+ */
+ @UnsupportedAppUsage
+ NotifyRespInd(PduHeaders headers) {
+ super(headers);
+ }
+
+ /**
+ * Get X-Mms-Report-Allowed field value.
+ *
+ * @return the X-Mms-Report-Allowed value
+ */
+ public int getReportAllowed() {
+ return mPduHeaders.getOctet(PduHeaders.REPORT_ALLOWED);
+ }
+
+ /**
+ * Set X-Mms-Report-Allowed field value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ * RuntimeException if an undeclared error occurs.
+ */
+ @UnsupportedAppUsage
+ public void setReportAllowed(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.REPORT_ALLOWED);
+ }
+
+ /**
+ * Set X-Mms-Status field value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ * RuntimeException if an undeclared error occurs.
+ */
+ @UnsupportedAppUsage
+ public void setStatus(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.STATUS);
+ }
+
+ /**
+ * GetX-Mms-Status field value.
+ *
+ * @return the X-Mms-Status value
+ */
+ public int getStatus() {
+ return mPduHeaders.getOctet(PduHeaders.STATUS);
+ }
+
+ /**
+ * Get X-Mms-Transaction-Id field value.
+ *
+ * @return the X-Mms-Report-Allowed value
+ */
+ public byte[] getTransactionId() {
+ return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+ }
+
+ /**
+ * Set X-Mms-Transaction-Id field value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ * RuntimeException if an undeclared error occurs.
+ */
+ @UnsupportedAppUsage
+ public void setTransactionId(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+ }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/PduBody.java b/telephony/java/com/google/android/mms/pdu/PduBody.java
new file mode 100644
index 0000000..51914e4
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/PduBody.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Vector;
+
+public class PduBody {
+ private Vector<PduPart> mParts = null;
+
+ private Map<String, PduPart> mPartMapByContentId = null;
+ private Map<String, PduPart> mPartMapByContentLocation = null;
+ private Map<String, PduPart> mPartMapByName = null;
+ private Map<String, PduPart> mPartMapByFileName = null;
+
+ /**
+ * Constructor.
+ */
+ @UnsupportedAppUsage
+ public PduBody() {
+ mParts = new Vector<PduPart>();
+
+ mPartMapByContentId = new HashMap<String, PduPart>();
+ mPartMapByContentLocation = new HashMap<String, PduPart>();
+ mPartMapByName = new HashMap<String, PduPart>();
+ mPartMapByFileName = new HashMap<String, PduPart>();
+ }
+
+ private void putPartToMaps(PduPart part) {
+ // Put part to mPartMapByContentId.
+ byte[] contentId = part.getContentId();
+ if(null != contentId) {
+ mPartMapByContentId.put(new String(contentId), part);
+ }
+
+ // Put part to mPartMapByContentLocation.
+ byte[] contentLocation = part.getContentLocation();
+ if(null != contentLocation) {
+ String clc = new String(contentLocation);
+ mPartMapByContentLocation.put(clc, part);
+ }
+
+ // Put part to mPartMapByName.
+ byte[] name = part.getName();
+ if(null != name) {
+ String clc = new String(name);
+ mPartMapByName.put(clc, part);
+ }
+
+ // Put part to mPartMapByFileName.
+ byte[] fileName = part.getFilename();
+ if(null != fileName) {
+ String clc = new String(fileName);
+ mPartMapByFileName.put(clc, part);
+ }
+ }
+
+ /**
+ * Appends the specified part to the end of this body.
+ *
+ * @param part part to be appended
+ * @return true when success, false when fail
+ * @throws NullPointerException when part is null
+ */
+ @UnsupportedAppUsage
+ public boolean addPart(PduPart part) {
+ if(null == part) {
+ throw new NullPointerException();
+ }
+
+ putPartToMaps(part);
+ return mParts.add(part);
+ }
+
+ /**
+ * Inserts the specified part at the specified position.
+ *
+ * @param index index at which the specified part is to be inserted
+ * @param part part to be inserted
+ * @throws NullPointerException when part is null
+ */
+ @UnsupportedAppUsage
+ public void addPart(int index, PduPart part) {
+ if(null == part) {
+ throw new NullPointerException();
+ }
+
+ putPartToMaps(part);
+ mParts.add(index, part);
+ }
+
+ /**
+ * Removes the part at the specified position.
+ *
+ * @param index index of the part to return
+ * @return part at the specified index
+ */
+ @UnsupportedAppUsage
+ public PduPart removePart(int index) {
+ return mParts.remove(index);
+ }
+
+ /**
+ * Remove all of the parts.
+ */
+ public void removeAll() {
+ mParts.clear();
+ }
+
+ /**
+ * Get the part at the specified position.
+ *
+ * @param index index of the part to return
+ * @return part at the specified index
+ */
+ @UnsupportedAppUsage
+ public PduPart getPart(int index) {
+ return mParts.get(index);
+ }
+
+ /**
+ * Get the index of the specified part.
+ *
+ * @param part the part object
+ * @return index the index of the first occurrence of the part in this body
+ */
+ @UnsupportedAppUsage
+ public int getPartIndex(PduPart part) {
+ return mParts.indexOf(part);
+ }
+
+ /**
+ * Get the number of parts.
+ *
+ * @return the number of parts
+ */
+ @UnsupportedAppUsage
+ public int getPartsNum() {
+ return mParts.size();
+ }
+
+ /**
+ * Get pdu part by content id.
+ *
+ * @param cid the value of content id.
+ * @return the pdu part.
+ */
+ @UnsupportedAppUsage
+ public PduPart getPartByContentId(String cid) {
+ return mPartMapByContentId.get(cid);
+ }
+
+ /**
+ * Get pdu part by Content-Location. Content-Location of part is
+ * the same as filename and name(param of content-type).
+ *
+ * @param fileName the value of filename.
+ * @return the pdu part.
+ */
+ @UnsupportedAppUsage
+ public PduPart getPartByContentLocation(String contentLocation) {
+ return mPartMapByContentLocation.get(contentLocation);
+ }
+
+ /**
+ * Get pdu part by name.
+ *
+ * @param fileName the value of filename.
+ * @return the pdu part.
+ */
+ @UnsupportedAppUsage
+ public PduPart getPartByName(String name) {
+ return mPartMapByName.get(name);
+ }
+
+ /**
+ * Get pdu part by filename.
+ *
+ * @param fileName the value of filename.
+ * @return the pdu part.
+ */
+ @UnsupportedAppUsage
+ public PduPart getPartByFileName(String filename) {
+ return mPartMapByFileName.get(filename);
+ }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/PduComposer.java b/telephony/java/com/google/android/mms/pdu/PduComposer.java
new file mode 100644
index 0000000..e24bf21
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/PduComposer.java
@@ -0,0 +1,1229 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-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.google.android.mms.pdu;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.text.TextUtils;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.HashMap;
+
+public class PduComposer {
+ /**
+ * Address type.
+ */
+ static private final int PDU_PHONE_NUMBER_ADDRESS_TYPE = 1;
+ static private final int PDU_EMAIL_ADDRESS_TYPE = 2;
+ static private final int PDU_IPV4_ADDRESS_TYPE = 3;
+ static private final int PDU_IPV6_ADDRESS_TYPE = 4;
+ static private final int PDU_UNKNOWN_ADDRESS_TYPE = 5;
+
+ /**
+ * Address regular expression string.
+ */
+ static final String REGEXP_PHONE_NUMBER_ADDRESS_TYPE = "\\+?[0-9|\\.|\\-]+";
+ static final String REGEXP_EMAIL_ADDRESS_TYPE = "[a-zA-Z| ]*\\<{0,1}[a-zA-Z| ]+@{1}" +
+ "[a-zA-Z| ]+\\.{1}[a-zA-Z| ]+\\>{0,1}";
+ static final String REGEXP_IPV6_ADDRESS_TYPE =
+ "[a-fA-F]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}" +
+ "[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}" +
+ "[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}";
+ static final String REGEXP_IPV4_ADDRESS_TYPE = "[0-9]{1,3}\\.{1}[0-9]{1,3}\\.{1}" +
+ "[0-9]{1,3}\\.{1}[0-9]{1,3}";
+
+ /**
+ * The postfix strings of address.
+ */
+ static final String STRING_PHONE_NUMBER_ADDRESS_TYPE = "/TYPE=PLMN";
+ static final String STRING_IPV4_ADDRESS_TYPE = "/TYPE=IPV4";
+ static final String STRING_IPV6_ADDRESS_TYPE = "/TYPE=IPV6";
+
+ /**
+ * Error values.
+ */
+ static private final int PDU_COMPOSE_SUCCESS = 0;
+ static private final int PDU_COMPOSE_CONTENT_ERROR = 1;
+ static private final int PDU_COMPOSE_FIELD_NOT_SET = 2;
+ static private final int PDU_COMPOSE_FIELD_NOT_SUPPORTED = 3;
+
+ /**
+ * WAP values defined in WSP spec.
+ */
+ static private final int QUOTED_STRING_FLAG = 34;
+ static private final int END_STRING_FLAG = 0;
+ static private final int LENGTH_QUOTE = 31;
+ static private final int TEXT_MAX = 127;
+ static private final int SHORT_INTEGER_MAX = 127;
+ static private final int LONG_INTEGER_LENGTH_MAX = 8;
+
+ /**
+ * Block size when read data from InputStream.
+ */
+ static private final int PDU_COMPOSER_BLOCK_SIZE = 1024;
+
+ /**
+ * The output message.
+ */
+ @UnsupportedAppUsage
+ protected ByteArrayOutputStream mMessage = null;
+
+ /**
+ * The PDU.
+ */
+ @UnsupportedAppUsage
+ private GenericPdu mPdu = null;
+
+ /**
+ * Current visiting position of the mMessage.
+ */
+ @UnsupportedAppUsage
+ protected int mPosition = 0;
+
+ /**
+ * Message compose buffer stack.
+ */
+ @UnsupportedAppUsage
+ private BufferStack mStack = null;
+
+ /**
+ * Content resolver.
+ */
+ @UnsupportedAppUsage
+ private final ContentResolver mResolver;
+
+ /**
+ * Header of this pdu.
+ */
+ @UnsupportedAppUsage
+ private PduHeaders mPduHeader = null;
+
+ /**
+ * Map of all content type
+ */
+ @UnsupportedAppUsage
+ private static HashMap<String, Integer> mContentTypeMap = null;
+
+ static {
+ mContentTypeMap = new HashMap<String, Integer>();
+
+ int i;
+ for (i = 0; i < PduContentTypes.contentTypes.length; i++) {
+ mContentTypeMap.put(PduContentTypes.contentTypes[i], i);
+ }
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param context the context
+ * @param pdu the pdu to be composed
+ */
+ @UnsupportedAppUsage
+ public PduComposer(Context context, GenericPdu pdu) {
+ mPdu = pdu;
+ mResolver = context.getContentResolver();
+ mPduHeader = pdu.getPduHeaders();
+ mStack = new BufferStack();
+ mMessage = new ByteArrayOutputStream();
+ mPosition = 0;
+ }
+
+ /**
+ * Make the message. No need to check whether mandatory fields are set,
+ * because the constructors of outgoing pdus are taking care of this.
+ *
+ * @return OutputStream of maked message. Return null if
+ * the PDU is invalid.
+ */
+ @UnsupportedAppUsage
+ public byte[] make() {
+ // Get Message-type.
+ int type = mPdu.getMessageType();
+
+ /* make the message */
+ switch (type) {
+ case PduHeaders.MESSAGE_TYPE_SEND_REQ:
+ case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
+ if (makeSendRetrievePdu(type) != PDU_COMPOSE_SUCCESS) {
+ return null;
+ }
+ break;
+ case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
+ if (makeNotifyResp() != PDU_COMPOSE_SUCCESS) {
+ return null;
+ }
+ break;
+ case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
+ if (makeAckInd() != PDU_COMPOSE_SUCCESS) {
+ return null;
+ }
+ break;
+ case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
+ if (makeReadRecInd() != PDU_COMPOSE_SUCCESS) {
+ return null;
+ }
+ break;
+ default:
+ return null;
+ }
+
+ return mMessage.toByteArray();
+ }
+
+ /**
+ * Copy buf to mMessage.
+ */
+ @UnsupportedAppUsage
+ protected void arraycopy(byte[] buf, int pos, int length) {
+ mMessage.write(buf, pos, length);
+ mPosition = mPosition + length;
+ }
+
+ /**
+ * Append a byte to mMessage.
+ */
+ protected void append(int value) {
+ mMessage.write(value);
+ mPosition ++;
+ }
+
+ /**
+ * Append short integer value to mMessage.
+ * This implementation doesn't check the validity of parameter, since it
+ * assumes that the values are validated in the GenericPdu setter methods.
+ */
+ @UnsupportedAppUsage
+ protected void appendShortInteger(int value) {
+ /*
+ * From WAP-230-WSP-20010705-a:
+ * Short-integer = OCTET
+ * ; Integers in range 0-127 shall be encoded as a one octet value
+ * ; with the most significant bit set to one (1xxx xxxx) and with
+ * ; the value in the remaining least significant bits.
+ * In our implementation, only low 7 bits are stored and otherwise
+ * bits are ignored.
+ */
+ append((value | 0x80) & 0xff);
+ }
+
+ /**
+ * Append an octet number between 128 and 255 into mMessage.
+ * NOTE:
+ * A value between 0 and 127 should be appended by using appendShortInteger.
+ * This implementation doesn't check the validity of parameter, since it
+ * assumes that the values are validated in the GenericPdu setter methods.
+ */
+ @UnsupportedAppUsage
+ protected void appendOctet(int number) {
+ append(number);
+ }
+
+ /**
+ * Append a short length into mMessage.
+ * This implementation doesn't check the validity of parameter, since it
+ * assumes that the values are validated in the GenericPdu setter methods.
+ */
+ protected void appendShortLength(int value) {
+ /*
+ * From WAP-230-WSP-20010705-a:
+ * Short-length = <Any octet 0-30>
+ */
+ append(value);
+ }
+
+ /**
+ * Append long integer into mMessage. it's used for really long integers.
+ * This implementation doesn't check the validity of parameter, since it
+ * assumes that the values are validated in the GenericPdu setter methods.
+ */
+ @UnsupportedAppUsage
+ protected void appendLongInteger(long longInt) {
+ /*
+ * From WAP-230-WSP-20010705-a:
+ * Long-integer = Short-length Multi-octet-integer
+ * ; The Short-length indicates the length of the Multi-octet-integer
+ * Multi-octet-integer = 1*30 OCTET
+ * ; The content octets shall be an unsigned integer value with the
+ * ; most significant octet encoded first (big-endian representation).
+ * ; The minimum number of octets must be used to encode the value.
+ */
+ int size;
+ long temp = longInt;
+
+ // Count the length of the long integer.
+ for(size = 0; (temp != 0) && (size < LONG_INTEGER_LENGTH_MAX); size++) {
+ temp = (temp >>> 8);
+ }
+
+ // Set Length.
+ appendShortLength(size);
+
+ // Count and set the long integer.
+ int i;
+ int shift = (size -1) * 8;
+
+ for (i = 0; i < size; i++) {
+ append((int)((longInt >>> shift) & 0xff));
+ shift = shift - 8;
+ }
+ }
+
+ /**
+ * Append text string into mMessage.
+ * This implementation doesn't check the validity of parameter, since it
+ * assumes that the values are validated in the GenericPdu setter methods.
+ */
+ @UnsupportedAppUsage
+ protected void appendTextString(byte[] text) {
+ /*
+ * From WAP-230-WSP-20010705-a:
+ * Text-string = [Quote] *TEXT End-of-string
+ * ; If the first character in the TEXT is in the range of 128-255,
+ * ; a Quote character must precede it. Otherwise the Quote character
+ * ;must be omitted. The Quote is not part of the contents.
+ */
+ if (((text[0])&0xff) > TEXT_MAX) { // No need to check for <= 255
+ append(TEXT_MAX);
+ }
+
+ arraycopy(text, 0, text.length);
+ append(0);
+ }
+
+ /**
+ * Append text string into mMessage.
+ * This implementation doesn't check the validity of parameter, since it
+ * assumes that the values are validated in the GenericPdu setter methods.
+ */
+ @UnsupportedAppUsage
+ protected void appendTextString(String str) {
+ /*
+ * From WAP-230-WSP-20010705-a:
+ * Text-string = [Quote] *TEXT End-of-string
+ * ; If the first character in the TEXT is in the range of 128-255,
+ * ; a Quote character must precede it. Otherwise the Quote character
+ * ;must be omitted. The Quote is not part of the contents.
+ */
+ appendTextString(str.getBytes());
+ }
+
+ /**
+ * Append encoded string value to mMessage.
+ * This implementation doesn't check the validity of parameter, since it
+ * assumes that the values are validated in the GenericPdu setter methods.
+ */
+ @UnsupportedAppUsage
+ protected void appendEncodedString(EncodedStringValue enStr) {
+ /*
+ * From OMA-TS-MMS-ENC-V1_3-20050927-C:
+ * Encoded-string-value = Text-string | Value-length Char-set Text-string
+ */
+ assert(enStr != null);
+
+ int charset = enStr.getCharacterSet();
+ byte[] textString = enStr.getTextString();
+ if (null == textString) {
+ return;
+ }
+
+ /*
+ * In the implementation of EncodedStringValue, the charset field will
+ * never be 0. It will always be composed as
+ * Encoded-string-value = Value-length Char-set Text-string
+ */
+ mStack.newbuf();
+ PositionMarker start = mStack.mark();
+
+ appendShortInteger(charset);
+ appendTextString(textString);
+
+ int len = start.getLength();
+ mStack.pop();
+ appendValueLength(len);
+ mStack.copy();
+ }
+
+ /**
+ * Append uintvar integer into mMessage.
+ * This implementation doesn't check the validity of parameter, since it
+ * assumes that the values are validated in the GenericPdu setter methods.
+ */
+ @UnsupportedAppUsage
+ protected void appendUintvarInteger(long value) {
+ /*
+ * From WAP-230-WSP-20010705-a:
+ * To encode a large unsigned integer, split it into 7-bit fragments
+ * and place them in the payloads of multiple octets. The most significant
+ * bits are placed in the first octets with the least significant bits
+ * ending up in the last octet. All octets MUST set the Continue bit to 1
+ * except the last octet, which MUST set the Continue bit to 0.
+ */
+ int i;
+ long max = SHORT_INTEGER_MAX;
+
+ for (i = 0; i < 5; i++) {
+ if (value < max) {
+ break;
+ }
+
+ max = (max << 7) | 0x7fl;
+ }
+
+ while(i > 0) {
+ long temp = value >>> (i * 7);
+ temp = temp & 0x7f;
+
+ append((int)((temp | 0x80) & 0xff));
+
+ i--;
+ }
+
+ append((int)(value & 0x7f));
+ }
+
+ /**
+ * Append date value into mMessage.
+ * This implementation doesn't check the validity of parameter, since it
+ * assumes that the values are validated in the GenericPdu setter methods.
+ */
+ protected void appendDateValue(long date) {
+ /*
+ * From OMA-TS-MMS-ENC-V1_3-20050927-C:
+ * Date-value = Long-integer
+ */
+ appendLongInteger(date);
+ }
+
+ /**
+ * Append value length to mMessage.
+ * This implementation doesn't check the validity of parameter, since it
+ * assumes that the values are validated in the GenericPdu setter methods.
+ */
+ @UnsupportedAppUsage
+ protected void appendValueLength(long value) {
+ /*
+ * From WAP-230-WSP-20010705-a:
+ * Value-length = Short-length | (Length-quote Length)
+ * ; Value length is used to indicate the length of the value to follow
+ * Short-length = <Any octet 0-30>
+ * Length-quote = <Octet 31>
+ * Length = Uintvar-integer
+ */
+ if (value < LENGTH_QUOTE) {
+ appendShortLength((int) value);
+ return;
+ }
+
+ append(LENGTH_QUOTE);
+ appendUintvarInteger(value);
+ }
+
+ /**
+ * Append quoted string to mMessage.
+ * This implementation doesn't check the validity of parameter, since it
+ * assumes that the values are validated in the GenericPdu setter methods.
+ */
+ @UnsupportedAppUsage
+ protected void appendQuotedString(byte[] text) {
+ /*
+ * From WAP-230-WSP-20010705-a:
+ * Quoted-string = <Octet 34> *TEXT End-of-string
+ * ;The TEXT encodes an RFC2616 Quoted-string with the enclosing
+ * ;quotation-marks <"> removed.
+ */
+ append(QUOTED_STRING_FLAG);
+ arraycopy(text, 0, text.length);
+ append(END_STRING_FLAG);
+ }
+
+ /**
+ * Append quoted string to mMessage.
+ * This implementation doesn't check the validity of parameter, since it
+ * assumes that the values are validated in the GenericPdu setter methods.
+ */
+ @UnsupportedAppUsage
+ protected void appendQuotedString(String str) {
+ /*
+ * From WAP-230-WSP-20010705-a:
+ * Quoted-string = <Octet 34> *TEXT End-of-string
+ * ;The TEXT encodes an RFC2616 Quoted-string with the enclosing
+ * ;quotation-marks <"> removed.
+ */
+ appendQuotedString(str.getBytes());
+ }
+
+ private EncodedStringValue appendAddressType(EncodedStringValue address) {
+ EncodedStringValue temp = null;
+
+ try {
+ int addressType = checkAddressType(address.getString());
+ temp = EncodedStringValue.copy(address);
+ if (PDU_PHONE_NUMBER_ADDRESS_TYPE == addressType) {
+ // Phone number.
+ temp.appendTextString(STRING_PHONE_NUMBER_ADDRESS_TYPE.getBytes());
+ } else if (PDU_IPV4_ADDRESS_TYPE == addressType) {
+ // Ipv4 address.
+ temp.appendTextString(STRING_IPV4_ADDRESS_TYPE.getBytes());
+ } else if (PDU_IPV6_ADDRESS_TYPE == addressType) {
+ // Ipv6 address.
+ temp.appendTextString(STRING_IPV6_ADDRESS_TYPE.getBytes());
+ }
+ } catch (NullPointerException e) {
+ return null;
+ }
+
+ return temp;
+ }
+
+ /**
+ * Append header to mMessage.
+ */
+ @UnsupportedAppUsage
+ private int appendHeader(int field) {
+ switch (field) {
+ case PduHeaders.MMS_VERSION:
+ appendOctet(field);
+
+ int version = mPduHeader.getOctet(field);
+ if (0 == version) {
+ appendShortInteger(PduHeaders.CURRENT_MMS_VERSION);
+ } else {
+ appendShortInteger(version);
+ }
+
+ break;
+
+ case PduHeaders.MESSAGE_ID:
+ case PduHeaders.TRANSACTION_ID:
+ byte[] textString = mPduHeader.getTextString(field);
+ if (null == textString) {
+ return PDU_COMPOSE_FIELD_NOT_SET;
+ }
+
+ appendOctet(field);
+ appendTextString(textString);
+ break;
+
+ case PduHeaders.TO:
+ case PduHeaders.BCC:
+ case PduHeaders.CC:
+ EncodedStringValue[] addr = mPduHeader.getEncodedStringValues(field);
+
+ if (null == addr) {
+ return PDU_COMPOSE_FIELD_NOT_SET;
+ }
+
+ EncodedStringValue temp;
+ for (int i = 0; i < addr.length; i++) {
+ temp = appendAddressType(addr[i]);
+ if (temp == null) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ appendOctet(field);
+ appendEncodedString(temp);
+ }
+ break;
+
+ case PduHeaders.FROM:
+ // Value-length (Address-present-token Encoded-string-value | Insert-address-token)
+ appendOctet(field);
+
+ EncodedStringValue from = mPduHeader.getEncodedStringValue(field);
+ if ((from == null)
+ || TextUtils.isEmpty(from.getString())
+ || new String(from.getTextString()).equals(
+ PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR)) {
+ // Length of from = 1
+ append(1);
+ // Insert-address-token = <Octet 129>
+ append(PduHeaders.FROM_INSERT_ADDRESS_TOKEN);
+ } else {
+ mStack.newbuf();
+ PositionMarker fstart = mStack.mark();
+
+ // Address-present-token = <Octet 128>
+ append(PduHeaders.FROM_ADDRESS_PRESENT_TOKEN);
+
+ temp = appendAddressType(from);
+ if (temp == null) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ appendEncodedString(temp);
+
+ int flen = fstart.getLength();
+ mStack.pop();
+ appendValueLength(flen);
+ mStack.copy();
+ }
+ break;
+
+ case PduHeaders.READ_STATUS:
+ case PduHeaders.STATUS:
+ case PduHeaders.REPORT_ALLOWED:
+ case PduHeaders.PRIORITY:
+ case PduHeaders.DELIVERY_REPORT:
+ case PduHeaders.READ_REPORT:
+ case PduHeaders.RETRIEVE_STATUS:
+ int octet = mPduHeader.getOctet(field);
+ if (0 == octet) {
+ return PDU_COMPOSE_FIELD_NOT_SET;
+ }
+
+ appendOctet(field);
+ appendOctet(octet);
+ break;
+
+ case PduHeaders.DATE:
+ long date = mPduHeader.getLongInteger(field);
+ if (-1 == date) {
+ return PDU_COMPOSE_FIELD_NOT_SET;
+ }
+
+ appendOctet(field);
+ appendDateValue(date);
+ break;
+
+ case PduHeaders.SUBJECT:
+ case PduHeaders.RETRIEVE_TEXT:
+ EncodedStringValue enString =
+ mPduHeader.getEncodedStringValue(field);
+ if (null == enString) {
+ return PDU_COMPOSE_FIELD_NOT_SET;
+ }
+
+ appendOctet(field);
+ appendEncodedString(enString);
+ break;
+
+ case PduHeaders.MESSAGE_CLASS:
+ byte[] messageClass = mPduHeader.getTextString(field);
+ if (null == messageClass) {
+ return PDU_COMPOSE_FIELD_NOT_SET;
+ }
+
+ appendOctet(field);
+ if (Arrays.equals(messageClass,
+ PduHeaders.MESSAGE_CLASS_ADVERTISEMENT_STR.getBytes())) {
+ appendOctet(PduHeaders.MESSAGE_CLASS_ADVERTISEMENT);
+ } else if (Arrays.equals(messageClass,
+ PduHeaders.MESSAGE_CLASS_AUTO_STR.getBytes())) {
+ appendOctet(PduHeaders.MESSAGE_CLASS_AUTO);
+ } else if (Arrays.equals(messageClass,
+ PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes())) {
+ appendOctet(PduHeaders.MESSAGE_CLASS_PERSONAL);
+ } else if (Arrays.equals(messageClass,
+ PduHeaders.MESSAGE_CLASS_INFORMATIONAL_STR.getBytes())) {
+ appendOctet(PduHeaders.MESSAGE_CLASS_INFORMATIONAL);
+ } else {
+ appendTextString(messageClass);
+ }
+ break;
+
+ case PduHeaders.EXPIRY:
+ long expiry = mPduHeader.getLongInteger(field);
+ if (-1 == expiry) {
+ return PDU_COMPOSE_FIELD_NOT_SET;
+ }
+
+ appendOctet(field);
+
+ mStack.newbuf();
+ PositionMarker expiryStart = mStack.mark();
+
+ append(PduHeaders.VALUE_RELATIVE_TOKEN);
+ appendLongInteger(expiry);
+
+ int expiryLength = expiryStart.getLength();
+ mStack.pop();
+ appendValueLength(expiryLength);
+ mStack.copy();
+ break;
+
+ default:
+ return PDU_COMPOSE_FIELD_NOT_SUPPORTED;
+ }
+
+ return PDU_COMPOSE_SUCCESS;
+ }
+
+ /**
+ * Make ReadRec.Ind.
+ */
+ private int makeReadRecInd() {
+ if (mMessage == null) {
+ mMessage = new ByteArrayOutputStream();
+ mPosition = 0;
+ }
+
+ // X-Mms-Message-Type
+ appendOctet(PduHeaders.MESSAGE_TYPE);
+ appendOctet(PduHeaders.MESSAGE_TYPE_READ_REC_IND);
+
+ // X-Mms-MMS-Version
+ if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ // Message-ID
+ if (appendHeader(PduHeaders.MESSAGE_ID) != PDU_COMPOSE_SUCCESS) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ // To
+ if (appendHeader(PduHeaders.TO) != PDU_COMPOSE_SUCCESS) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ // From
+ if (appendHeader(PduHeaders.FROM) != PDU_COMPOSE_SUCCESS) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ // Date Optional
+ appendHeader(PduHeaders.DATE);
+
+ // X-Mms-Read-Status
+ if (appendHeader(PduHeaders.READ_STATUS) != PDU_COMPOSE_SUCCESS) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ // X-Mms-Applic-ID Optional(not support)
+ // X-Mms-Reply-Applic-ID Optional(not support)
+ // X-Mms-Aux-Applic-Info Optional(not support)
+
+ return PDU_COMPOSE_SUCCESS;
+ }
+
+ /**
+ * Make NotifyResp.Ind.
+ */
+ private int makeNotifyResp() {
+ if (mMessage == null) {
+ mMessage = new ByteArrayOutputStream();
+ mPosition = 0;
+ }
+
+ // X-Mms-Message-Type
+ appendOctet(PduHeaders.MESSAGE_TYPE);
+ appendOctet(PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND);
+
+ // X-Mms-Transaction-ID
+ if (appendHeader(PduHeaders.TRANSACTION_ID) != PDU_COMPOSE_SUCCESS) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ // X-Mms-MMS-Version
+ if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ // X-Mms-Status
+ if (appendHeader(PduHeaders.STATUS) != PDU_COMPOSE_SUCCESS) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ // X-Mms-Report-Allowed Optional (not support)
+ return PDU_COMPOSE_SUCCESS;
+ }
+
+ /**
+ * Make Acknowledge.Ind.
+ */
+ private int makeAckInd() {
+ if (mMessage == null) {
+ mMessage = new ByteArrayOutputStream();
+ mPosition = 0;
+ }
+
+ // X-Mms-Message-Type
+ appendOctet(PduHeaders.MESSAGE_TYPE);
+ appendOctet(PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND);
+
+ // X-Mms-Transaction-ID
+ if (appendHeader(PduHeaders.TRANSACTION_ID) != PDU_COMPOSE_SUCCESS) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ // X-Mms-MMS-Version
+ if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ // X-Mms-Report-Allowed Optional
+ appendHeader(PduHeaders.REPORT_ALLOWED);
+
+ return PDU_COMPOSE_SUCCESS;
+ }
+
+ /**
+ * Make Send.req.
+ */
+ private int makeSendRetrievePdu(int type) {
+ if (mMessage == null) {
+ mMessage = new ByteArrayOutputStream();
+ mPosition = 0;
+ }
+
+ // X-Mms-Message-Type
+ appendOctet(PduHeaders.MESSAGE_TYPE);
+ appendOctet(type);
+
+ // X-Mms-Transaction-ID
+ appendOctet(PduHeaders.TRANSACTION_ID);
+
+ byte[] trid = mPduHeader.getTextString(PduHeaders.TRANSACTION_ID);
+ if (trid == null) {
+ // Transaction-ID should be set(by Transaction) before make().
+ throw new IllegalArgumentException("Transaction-ID is null.");
+ }
+ appendTextString(trid);
+
+ // X-Mms-MMS-Version
+ if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ // Date Date-value Optional.
+ appendHeader(PduHeaders.DATE);
+
+ // From
+ if (appendHeader(PduHeaders.FROM) != PDU_COMPOSE_SUCCESS) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ boolean recipient = false;
+
+ // To
+ if (appendHeader(PduHeaders.TO) != PDU_COMPOSE_CONTENT_ERROR) {
+ recipient = true;
+ }
+
+ // Cc
+ if (appendHeader(PduHeaders.CC) != PDU_COMPOSE_CONTENT_ERROR) {
+ recipient = true;
+ }
+
+ // Bcc
+ if (appendHeader(PduHeaders.BCC) != PDU_COMPOSE_CONTENT_ERROR) {
+ recipient = true;
+ }
+
+ // Need at least one of "cc", "bcc" and "to".
+ if (false == recipient) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ // Subject Optional
+ appendHeader(PduHeaders.SUBJECT);
+
+ // X-Mms-Message-Class Optional
+ // Message-class-value = Class-identifier | Token-text
+ appendHeader(PduHeaders.MESSAGE_CLASS);
+
+ // X-Mms-Expiry Optional
+ appendHeader(PduHeaders.EXPIRY);
+
+ // X-Mms-Priority Optional
+ appendHeader(PduHeaders.PRIORITY);
+
+ // X-Mms-Delivery-Report Optional
+ appendHeader(PduHeaders.DELIVERY_REPORT);
+
+ // X-Mms-Read-Report Optional
+ appendHeader(PduHeaders.READ_REPORT);
+
+ if (type == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF) {
+ // X-Mms-Retrieve-Status Optional
+ appendHeader(PduHeaders.RETRIEVE_STATUS);
+ // X-Mms-Retrieve-Text Optional
+ appendHeader(PduHeaders.RETRIEVE_TEXT);
+ }
+
+
+ // Content-Type
+ appendOctet(PduHeaders.CONTENT_TYPE);
+
+ // Message body
+ return makeMessageBody(type);
+ }
+
+ /**
+ * Make message body.
+ */
+ private int makeMessageBody(int type) {
+ // 1. add body informations
+ mStack.newbuf(); // Switching buffer because we need to
+
+ PositionMarker ctStart = mStack.mark();
+
+ // This contentTypeIdentifier should be used for type of attachment...
+ String contentType = new String(mPduHeader.getTextString(PduHeaders.CONTENT_TYPE));
+ Integer contentTypeIdentifier = mContentTypeMap.get(contentType);
+ if (contentTypeIdentifier == null) {
+ // content type is mandatory
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ appendShortInteger(contentTypeIdentifier.intValue());
+
+ // content-type parameter: start
+ PduBody body;
+ if (type == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF) {
+ body = ((RetrieveConf) mPdu).getBody();
+ } else {
+ body = ((SendReq) mPdu).getBody();
+ }
+ if (null == body || body.getPartsNum() == 0) {
+ // empty message
+ appendUintvarInteger(0);
+ mStack.pop();
+ mStack.copy();
+ return PDU_COMPOSE_SUCCESS;
+ }
+
+ PduPart part;
+ try {
+ part = body.getPart(0);
+
+ byte[] start = part.getContentId();
+ if (start != null) {
+ appendOctet(PduPart.P_DEP_START);
+ if (('<' == start[0]) && ('>' == start[start.length - 1])) {
+ appendTextString(start);
+ } else {
+ appendTextString("<" + new String(start) + ">");
+ }
+ }
+
+ // content-type parameter: type
+ appendOctet(PduPart.P_CT_MR_TYPE);
+ appendTextString(part.getContentType());
+ }
+ catch (ArrayIndexOutOfBoundsException e){
+ e.printStackTrace();
+ }
+
+ int ctLength = ctStart.getLength();
+ mStack.pop();
+ appendValueLength(ctLength);
+ mStack.copy();
+
+ // 3. add content
+ int partNum = body.getPartsNum();
+ appendUintvarInteger(partNum);
+ for (int i = 0; i < partNum; i++) {
+ part = body.getPart(i);
+ mStack.newbuf(); // Leaving space for header lengh and data length
+ PositionMarker attachment = mStack.mark();
+
+ mStack.newbuf(); // Leaving space for Content-Type length
+ PositionMarker contentTypeBegin = mStack.mark();
+
+ byte[] partContentType = part.getContentType();
+
+ if (partContentType == null) {
+ // content type is mandatory
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ // content-type value
+ Integer partContentTypeIdentifier =
+ mContentTypeMap.get(new String(partContentType));
+ if (partContentTypeIdentifier == null) {
+ appendTextString(partContentType);
+ } else {
+ appendShortInteger(partContentTypeIdentifier.intValue());
+ }
+
+ /* Content-type parameter : name.
+ * The value of name, filename, content-location is the same.
+ * Just one of them is enough for this PDU.
+ */
+ byte[] name = part.getName();
+
+ if (null == name) {
+ name = part.getFilename();
+
+ if (null == name) {
+ name = part.getContentLocation();
+
+ if (null == name) {
+ /* at lease one of name, filename, Content-location
+ * should be available.
+ */
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+ }
+ }
+ appendOctet(PduPart.P_DEP_NAME);
+ appendTextString(name);
+
+ // content-type parameter : charset
+ int charset = part.getCharset();
+ if (charset != 0) {
+ appendOctet(PduPart.P_CHARSET);
+ appendShortInteger(charset);
+ }
+
+ int contentTypeLength = contentTypeBegin.getLength();
+ mStack.pop();
+ appendValueLength(contentTypeLength);
+ mStack.copy();
+
+ // content id
+ byte[] contentId = part.getContentId();
+
+ if (null != contentId) {
+ appendOctet(PduPart.P_CONTENT_ID);
+ if (('<' == contentId[0]) && ('>' == contentId[contentId.length - 1])) {
+ appendQuotedString(contentId);
+ } else {
+ appendQuotedString("<" + new String(contentId) + ">");
+ }
+ }
+
+ // content-location
+ byte[] contentLocation = part.getContentLocation();
+ if (null != contentLocation) {
+ appendOctet(PduPart.P_CONTENT_LOCATION);
+ appendTextString(contentLocation);
+ }
+
+ // content
+ int headerLength = attachment.getLength();
+
+ int dataLength = 0; // Just for safety...
+ byte[] partData = part.getData();
+
+ if (partData != null) {
+ arraycopy(partData, 0, partData.length);
+ dataLength = partData.length;
+ } else {
+ InputStream cr = null;
+ try {
+ byte[] buffer = new byte[PDU_COMPOSER_BLOCK_SIZE];
+ cr = mResolver.openInputStream(part.getDataUri());
+ int len = 0;
+ while ((len = cr.read(buffer)) != -1) {
+ mMessage.write(buffer, 0, len);
+ mPosition += len;
+ dataLength += len;
+ }
+ } catch (FileNotFoundException e) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ } catch (IOException e) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ } catch (RuntimeException e) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ } finally {
+ if (cr != null) {
+ try {
+ cr.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ if (dataLength != (attachment.getLength() - headerLength)) {
+ throw new RuntimeException("BUG: Length sanity check failed");
+ }
+
+ mStack.pop();
+ appendUintvarInteger(headerLength);
+ appendUintvarInteger(dataLength);
+ mStack.copy();
+ }
+
+ return PDU_COMPOSE_SUCCESS;
+ }
+
+ /**
+ * Record current message informations.
+ */
+ static private class LengthRecordNode {
+ ByteArrayOutputStream currentMessage = null;
+ public int currentPosition = 0;
+
+ public LengthRecordNode next = null;
+ }
+
+ /**
+ * Mark current message position and stact size.
+ */
+ private class PositionMarker {
+ private int c_pos; // Current position
+ private int currentStackSize; // Current stack size
+
+ @UnsupportedAppUsage
+ int getLength() {
+ // If these assert fails, likely that you are finding the
+ // size of buffer that is deep in BufferStack you can only
+ // find the length of the buffer that is on top
+ if (currentStackSize != mStack.stackSize) {
+ throw new RuntimeException("BUG: Invalid call to getLength()");
+ }
+
+ return mPosition - c_pos;
+ }
+ }
+
+ /**
+ * This implementation can be OPTIMIZED to use only
+ * 2 buffers. This optimization involves changing BufferStack
+ * only... Its usage (interface) will not change.
+ */
+ private class BufferStack {
+ private LengthRecordNode stack = null;
+ private LengthRecordNode toCopy = null;
+
+ int stackSize = 0;
+
+ /**
+ * Create a new message buffer and push it into the stack.
+ */
+ @UnsupportedAppUsage
+ void newbuf() {
+ // You can't create a new buff when toCopy != null
+ // That is after calling pop() and before calling copy()
+ // If you do, it is a bug
+ if (toCopy != null) {
+ throw new RuntimeException("BUG: Invalid newbuf() before copy()");
+ }
+
+ LengthRecordNode temp = new LengthRecordNode();
+
+ temp.currentMessage = mMessage;
+ temp.currentPosition = mPosition;
+
+ temp.next = stack;
+ stack = temp;
+
+ stackSize = stackSize + 1;
+
+ mMessage = new ByteArrayOutputStream();
+ mPosition = 0;
+ }
+
+ /**
+ * Pop the message before and record current message in the stack.
+ */
+ @UnsupportedAppUsage
+ void pop() {
+ ByteArrayOutputStream currentMessage = mMessage;
+ int currentPosition = mPosition;
+
+ mMessage = stack.currentMessage;
+ mPosition = stack.currentPosition;
+
+ toCopy = stack;
+ // Re using the top element of the stack to avoid memory allocation
+
+ stack = stack.next;
+ stackSize = stackSize - 1;
+
+ toCopy.currentMessage = currentMessage;
+ toCopy.currentPosition = currentPosition;
+ }
+
+ /**
+ * Append current message to the message before.
+ */
+ @UnsupportedAppUsage
+ void copy() {
+ arraycopy(toCopy.currentMessage.toByteArray(), 0,
+ toCopy.currentPosition);
+
+ toCopy = null;
+ }
+
+ /**
+ * Mark current message position
+ */
+ @UnsupportedAppUsage
+ PositionMarker mark() {
+ PositionMarker m = new PositionMarker();
+
+ m.c_pos = mPosition;
+ m.currentStackSize = stackSize;
+
+ return m;
+ }
+ }
+
+ /**
+ * Check address type.
+ *
+ * @param address address string without the postfix stinng type,
+ * such as "/TYPE=PLMN", "/TYPE=IPv6" and "/TYPE=IPv4"
+ * @return PDU_PHONE_NUMBER_ADDRESS_TYPE if it is phone number,
+ * PDU_EMAIL_ADDRESS_TYPE if it is email address,
+ * PDU_IPV4_ADDRESS_TYPE if it is ipv4 address,
+ * PDU_IPV6_ADDRESS_TYPE if it is ipv6 address,
+ * PDU_UNKNOWN_ADDRESS_TYPE if it is unknown.
+ */
+ protected static int checkAddressType(String address) {
+ /**
+ * From OMA-TS-MMS-ENC-V1_3-20050927-C.pdf, section 8.
+ * address = ( e-mail / device-address / alphanum-shortcode / num-shortcode)
+ * e-mail = mailbox; to the definition of mailbox as described in
+ * section 3.4 of [RFC2822], but excluding the
+ * obsolete definitions as indicated by the "obs-" prefix.
+ * device-address = ( global-phone-number "/TYPE=PLMN" )
+ * / ( ipv4 "/TYPE=IPv4" ) / ( ipv6 "/TYPE=IPv6" )
+ * / ( escaped-value "/TYPE=" address-type )
+ *
+ * global-phone-number = ["+"] 1*( DIGIT / written-sep )
+ * written-sep =("-"/".")
+ *
+ * ipv4 = 1*3DIGIT 3( "." 1*3DIGIT ) ; IPv4 address value
+ *
+ * ipv6 = 4HEXDIG 7( ":" 4HEXDIG ) ; IPv6 address per RFC 2373
+ */
+
+ if (null == address) {
+ return PDU_UNKNOWN_ADDRESS_TYPE;
+ }
+
+ if (address.matches(REGEXP_IPV4_ADDRESS_TYPE)) {
+ // Ipv4 address.
+ return PDU_IPV4_ADDRESS_TYPE;
+ }else if (address.matches(REGEXP_PHONE_NUMBER_ADDRESS_TYPE)) {
+ // Phone number.
+ return PDU_PHONE_NUMBER_ADDRESS_TYPE;
+ } else if (address.matches(REGEXP_EMAIL_ADDRESS_TYPE)) {
+ // Email address.
+ return PDU_EMAIL_ADDRESS_TYPE;
+ } else if (address.matches(REGEXP_IPV6_ADDRESS_TYPE)) {
+ // Ipv6 address.
+ return PDU_IPV6_ADDRESS_TYPE;
+ } else {
+ // Unknown address.
+ return PDU_UNKNOWN_ADDRESS_TYPE;
+ }
+ }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/PduContentTypes.java b/telephony/java/com/google/android/mms/pdu/PduContentTypes.java
new file mode 100644
index 0000000..8551b2f
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/PduContentTypes.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+public class PduContentTypes {
+ /**
+ * All content types. From:
+ * http://www.openmobilealliance.org/tech/omna/omna-wsp-content-type.htm
+ */
+ @UnsupportedAppUsage
+ static final String[] contentTypes = {
+ "*/*", /* 0x00 */
+ "text/*", /* 0x01 */
+ "text/html", /* 0x02 */
+ "text/plain", /* 0x03 */
+ "text/x-hdml", /* 0x04 */
+ "text/x-ttml", /* 0x05 */
+ "text/x-vCalendar", /* 0x06 */
+ "text/x-vCard", /* 0x07 */
+ "text/vnd.wap.wml", /* 0x08 */
+ "text/vnd.wap.wmlscript", /* 0x09 */
+ "text/vnd.wap.wta-event", /* 0x0A */
+ "multipart/*", /* 0x0B */
+ "multipart/mixed", /* 0x0C */
+ "multipart/form-data", /* 0x0D */
+ "multipart/byterantes", /* 0x0E */
+ "multipart/alternative", /* 0x0F */
+ "application/*", /* 0x10 */
+ "application/java-vm", /* 0x11 */
+ "application/x-www-form-urlencoded", /* 0x12 */
+ "application/x-hdmlc", /* 0x13 */
+ "application/vnd.wap.wmlc", /* 0x14 */
+ "application/vnd.wap.wmlscriptc", /* 0x15 */
+ "application/vnd.wap.wta-eventc", /* 0x16 */
+ "application/vnd.wap.uaprof", /* 0x17 */
+ "application/vnd.wap.wtls-ca-certificate", /* 0x18 */
+ "application/vnd.wap.wtls-user-certificate", /* 0x19 */
+ "application/x-x509-ca-cert", /* 0x1A */
+ "application/x-x509-user-cert", /* 0x1B */
+ "image/*", /* 0x1C */
+ "image/gif", /* 0x1D */
+ "image/jpeg", /* 0x1E */
+ "image/tiff", /* 0x1F */
+ "image/png", /* 0x20 */
+ "image/vnd.wap.wbmp", /* 0x21 */
+ "application/vnd.wap.multipart.*", /* 0x22 */
+ "application/vnd.wap.multipart.mixed", /* 0x23 */
+ "application/vnd.wap.multipart.form-data", /* 0x24 */
+ "application/vnd.wap.multipart.byteranges", /* 0x25 */
+ "application/vnd.wap.multipart.alternative", /* 0x26 */
+ "application/xml", /* 0x27 */
+ "text/xml", /* 0x28 */
+ "application/vnd.wap.wbxml", /* 0x29 */
+ "application/x-x968-cross-cert", /* 0x2A */
+ "application/x-x968-ca-cert", /* 0x2B */
+ "application/x-x968-user-cert", /* 0x2C */
+ "text/vnd.wap.si", /* 0x2D */
+ "application/vnd.wap.sic", /* 0x2E */
+ "text/vnd.wap.sl", /* 0x2F */
+ "application/vnd.wap.slc", /* 0x30 */
+ "text/vnd.wap.co", /* 0x31 */
+ "application/vnd.wap.coc", /* 0x32 */
+ "application/vnd.wap.multipart.related", /* 0x33 */
+ "application/vnd.wap.sia", /* 0x34 */
+ "text/vnd.wap.connectivity-xml", /* 0x35 */
+ "application/vnd.wap.connectivity-wbxml", /* 0x36 */
+ "application/pkcs7-mime", /* 0x37 */
+ "application/vnd.wap.hashed-certificate", /* 0x38 */
+ "application/vnd.wap.signed-certificate", /* 0x39 */
+ "application/vnd.wap.cert-response", /* 0x3A */
+ "application/xhtml+xml", /* 0x3B */
+ "application/wml+xml", /* 0x3C */
+ "text/css", /* 0x3D */
+ "application/vnd.wap.mms-message", /* 0x3E */
+ "application/vnd.wap.rollover-certificate", /* 0x3F */
+ "application/vnd.wap.locc+wbxml", /* 0x40 */
+ "application/vnd.wap.loc+xml", /* 0x41 */
+ "application/vnd.syncml.dm+wbxml", /* 0x42 */
+ "application/vnd.syncml.dm+xml", /* 0x43 */
+ "application/vnd.syncml.notification", /* 0x44 */
+ "application/vnd.wap.xhtml+xml", /* 0x45 */
+ "application/vnd.wv.csp.cir", /* 0x46 */
+ "application/vnd.oma.dd+xml", /* 0x47 */
+ "application/vnd.oma.drm.message", /* 0x48 */
+ "application/vnd.oma.drm.content", /* 0x49 */
+ "application/vnd.oma.drm.rights+xml", /* 0x4A */
+ "application/vnd.oma.drm.rights+wbxml", /* 0x4B */
+ "application/vnd.wv.csp+xml", /* 0x4C */
+ "application/vnd.wv.csp+wbxml", /* 0x4D */
+ "application/vnd.syncml.ds.notification", /* 0x4E */
+ "audio/*", /* 0x4F */
+ "video/*", /* 0x50 */
+ "application/vnd.oma.dd2+xml", /* 0x51 */
+ "application/mikey" /* 0x52 */
+ };
+}
diff --git a/telephony/java/com/google/android/mms/pdu/PduHeaders.java b/telephony/java/com/google/android/mms/pdu/PduHeaders.java
new file mode 100644
index 0000000..b524464
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/PduHeaders.java
@@ -0,0 +1,733 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class PduHeaders {
+ /**
+ * All pdu header fields.
+ */
+ public static final int BCC = 0x81;
+ public static final int CC = 0x82;
+ public static final int CONTENT_LOCATION = 0x83;
+ public static final int CONTENT_TYPE = 0x84;
+ public static final int DATE = 0x85;
+ public static final int DELIVERY_REPORT = 0x86;
+ public static final int DELIVERY_TIME = 0x87;
+ public static final int EXPIRY = 0x88;
+ public static final int FROM = 0x89;
+ public static final int MESSAGE_CLASS = 0x8A;
+ public static final int MESSAGE_ID = 0x8B;
+ public static final int MESSAGE_TYPE = 0x8C;
+ public static final int MMS_VERSION = 0x8D;
+ public static final int MESSAGE_SIZE = 0x8E;
+ public static final int PRIORITY = 0x8F;
+
+ public static final int READ_REPLY = 0x90;
+ public static final int READ_REPORT = 0x90;
+ public static final int REPORT_ALLOWED = 0x91;
+ public static final int RESPONSE_STATUS = 0x92;
+ public static final int RESPONSE_TEXT = 0x93;
+ public static final int SENDER_VISIBILITY = 0x94;
+ public static final int STATUS = 0x95;
+ public static final int SUBJECT = 0x96;
+ public static final int TO = 0x97;
+ public static final int TRANSACTION_ID = 0x98;
+ public static final int RETRIEVE_STATUS = 0x99;
+ public static final int RETRIEVE_TEXT = 0x9A;
+ public static final int READ_STATUS = 0x9B;
+ public static final int REPLY_CHARGING = 0x9C;
+ public static final int REPLY_CHARGING_DEADLINE = 0x9D;
+ public static final int REPLY_CHARGING_ID = 0x9E;
+ public static final int REPLY_CHARGING_SIZE = 0x9F;
+
+ public static final int PREVIOUSLY_SENT_BY = 0xA0;
+ public static final int PREVIOUSLY_SENT_DATE = 0xA1;
+ public static final int STORE = 0xA2;
+ public static final int MM_STATE = 0xA3;
+ public static final int MM_FLAGS = 0xA4;
+ public static final int STORE_STATUS = 0xA5;
+ public static final int STORE_STATUS_TEXT = 0xA6;
+ public static final int STORED = 0xA7;
+ public static final int ATTRIBUTES = 0xA8;
+ public static final int TOTALS = 0xA9;
+ public static final int MBOX_TOTALS = 0xAA;
+ public static final int QUOTAS = 0xAB;
+ public static final int MBOX_QUOTAS = 0xAC;
+ public static final int MESSAGE_COUNT = 0xAD;
+ public static final int CONTENT = 0xAE;
+ public static final int START = 0xAF;
+
+ public static final int ADDITIONAL_HEADERS = 0xB0;
+ public static final int DISTRIBUTION_INDICATOR = 0xB1;
+ public static final int ELEMENT_DESCRIPTOR = 0xB2;
+ public static final int LIMIT = 0xB3;
+ public static final int RECOMMENDED_RETRIEVAL_MODE = 0xB4;
+ public static final int RECOMMENDED_RETRIEVAL_MODE_TEXT = 0xB5;
+ public static final int STATUS_TEXT = 0xB6;
+ public static final int APPLIC_ID = 0xB7;
+ public static final int REPLY_APPLIC_ID = 0xB8;
+ public static final int AUX_APPLIC_ID = 0xB9;
+ public static final int CONTENT_CLASS = 0xBA;
+ public static final int DRM_CONTENT = 0xBB;
+ public static final int ADAPTATION_ALLOWED = 0xBC;
+ public static final int REPLACE_ID = 0xBD;
+ public static final int CANCEL_ID = 0xBE;
+ public static final int CANCEL_STATUS = 0xBF;
+
+ /**
+ * X-Mms-Message-Type field types.
+ */
+ public static final int MESSAGE_TYPE_SEND_REQ = 0x80;
+ public static final int MESSAGE_TYPE_SEND_CONF = 0x81;
+ public static final int MESSAGE_TYPE_NOTIFICATION_IND = 0x82;
+ public static final int MESSAGE_TYPE_NOTIFYRESP_IND = 0x83;
+ public static final int MESSAGE_TYPE_RETRIEVE_CONF = 0x84;
+ public static final int MESSAGE_TYPE_ACKNOWLEDGE_IND = 0x85;
+ public static final int MESSAGE_TYPE_DELIVERY_IND = 0x86;
+ public static final int MESSAGE_TYPE_READ_REC_IND = 0x87;
+ public static final int MESSAGE_TYPE_READ_ORIG_IND = 0x88;
+ public static final int MESSAGE_TYPE_FORWARD_REQ = 0x89;
+ public static final int MESSAGE_TYPE_FORWARD_CONF = 0x8A;
+ public static final int MESSAGE_TYPE_MBOX_STORE_REQ = 0x8B;
+ public static final int MESSAGE_TYPE_MBOX_STORE_CONF = 0x8C;
+ public static final int MESSAGE_TYPE_MBOX_VIEW_REQ = 0x8D;
+ public static final int MESSAGE_TYPE_MBOX_VIEW_CONF = 0x8E;
+ public static final int MESSAGE_TYPE_MBOX_UPLOAD_REQ = 0x8F;
+ public static final int MESSAGE_TYPE_MBOX_UPLOAD_CONF = 0x90;
+ public static final int MESSAGE_TYPE_MBOX_DELETE_REQ = 0x91;
+ public static final int MESSAGE_TYPE_MBOX_DELETE_CONF = 0x92;
+ public static final int MESSAGE_TYPE_MBOX_DESCR = 0x93;
+ public static final int MESSAGE_TYPE_DELETE_REQ = 0x94;
+ public static final int MESSAGE_TYPE_DELETE_CONF = 0x95;
+ public static final int MESSAGE_TYPE_CANCEL_REQ = 0x96;
+ public static final int MESSAGE_TYPE_CANCEL_CONF = 0x97;
+
+ /**
+ * X-Mms-Delivery-Report |
+ * X-Mms-Read-Report |
+ * X-Mms-Report-Allowed |
+ * X-Mms-Sender-Visibility |
+ * X-Mms-Store |
+ * X-Mms-Stored |
+ * X-Mms-Totals |
+ * X-Mms-Quotas |
+ * X-Mms-Distribution-Indicator |
+ * X-Mms-DRM-Content |
+ * X-Mms-Adaptation-Allowed |
+ * field types.
+ */
+ public static final int VALUE_YES = 0x80;
+ public static final int VALUE_NO = 0x81;
+
+ /**
+ * Delivery-Time |
+ * Expiry and Reply-Charging-Deadline |
+ * field type components.
+ */
+ public static final int VALUE_ABSOLUTE_TOKEN = 0x80;
+ public static final int VALUE_RELATIVE_TOKEN = 0x81;
+
+ /**
+ * X-Mms-MMS-Version field types.
+ */
+ public static final int MMS_VERSION_1_3 = ((1 << 4) | 3);
+ public static final int MMS_VERSION_1_2 = ((1 << 4) | 2);
+ public static final int MMS_VERSION_1_1 = ((1 << 4) | 1);
+ public static final int MMS_VERSION_1_0 = ((1 << 4) | 0);
+
+ // Current version is 1.2.
+ public static final int CURRENT_MMS_VERSION = MMS_VERSION_1_2;
+
+ /**
+ * From field type components.
+ */
+ public static final int FROM_ADDRESS_PRESENT_TOKEN = 0x80;
+ public static final int FROM_INSERT_ADDRESS_TOKEN = 0x81;
+
+ public static final String FROM_ADDRESS_PRESENT_TOKEN_STR = "address-present-token";
+ public static final String FROM_INSERT_ADDRESS_TOKEN_STR = "insert-address-token";
+
+ /**
+ * X-Mms-Status Field.
+ */
+ public static final int STATUS_EXPIRED = 0x80;
+ public static final int STATUS_RETRIEVED = 0x81;
+ public static final int STATUS_REJECTED = 0x82;
+ public static final int STATUS_DEFERRED = 0x83;
+ public static final int STATUS_UNRECOGNIZED = 0x84;
+ public static final int STATUS_INDETERMINATE = 0x85;
+ public static final int STATUS_FORWARDED = 0x86;
+ public static final int STATUS_UNREACHABLE = 0x87;
+
+ /**
+ * MM-Flags field type components.
+ */
+ public static final int MM_FLAGS_ADD_TOKEN = 0x80;
+ public static final int MM_FLAGS_REMOVE_TOKEN = 0x81;
+ public static final int MM_FLAGS_FILTER_TOKEN = 0x82;
+
+ /**
+ * X-Mms-Message-Class field types.
+ */
+ public static final int MESSAGE_CLASS_PERSONAL = 0x80;
+ public static final int MESSAGE_CLASS_ADVERTISEMENT = 0x81;
+ public static final int MESSAGE_CLASS_INFORMATIONAL = 0x82;
+ public static final int MESSAGE_CLASS_AUTO = 0x83;
+
+ public static final String MESSAGE_CLASS_PERSONAL_STR = "personal";
+ public static final String MESSAGE_CLASS_ADVERTISEMENT_STR = "advertisement";
+ public static final String MESSAGE_CLASS_INFORMATIONAL_STR = "informational";
+ public static final String MESSAGE_CLASS_AUTO_STR = "auto";
+
+ /**
+ * X-Mms-Priority field types.
+ */
+ public static final int PRIORITY_LOW = 0x80;
+ public static final int PRIORITY_NORMAL = 0x81;
+ public static final int PRIORITY_HIGH = 0x82;
+
+ /**
+ * X-Mms-Response-Status field types.
+ */
+ public static final int RESPONSE_STATUS_OK = 0x80;
+ public static final int RESPONSE_STATUS_ERROR_UNSPECIFIED = 0x81;
+ public static final int RESPONSE_STATUS_ERROR_SERVICE_DENIED = 0x82;
+
+ public static final int RESPONSE_STATUS_ERROR_MESSAGE_FORMAT_CORRUPT = 0x83;
+ public static final int RESPONSE_STATUS_ERROR_SENDING_ADDRESS_UNRESOLVED = 0x84;
+
+ public static final int RESPONSE_STATUS_ERROR_MESSAGE_NOT_FOUND = 0x85;
+ public static final int RESPONSE_STATUS_ERROR_NETWORK_PROBLEM = 0x86;
+ public static final int RESPONSE_STATUS_ERROR_CONTENT_NOT_ACCEPTED = 0x87;
+ public static final int RESPONSE_STATUS_ERROR_UNSUPPORTED_MESSAGE = 0x88;
+ public static final int RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE = 0xC0;
+
+ public static final int RESPONSE_STATUS_ERROR_TRANSIENT_SENDNG_ADDRESS_UNRESOLVED = 0xC1;
+ public static final int RESPONSE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND = 0xC2;
+ public static final int RESPONSE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM = 0xC3;
+ public static final int RESPONSE_STATUS_ERROR_TRANSIENT_PARTIAL_SUCCESS = 0xC4;
+
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_FAILURE = 0xE0;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_SERVICE_DENIED = 0xE1;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_FORMAT_CORRUPT = 0xE2;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_SENDING_ADDRESS_UNRESOLVED = 0xE3;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND = 0xE4;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_CONTENT_NOT_ACCEPTED = 0xE5;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_LIMITATIONS_NOT_MET = 0xE6;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_REQUEST_NOT_ACCEPTED = 0xE6;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_FORWARDING_DENIED = 0xE8;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_NOT_SUPPORTED = 0xE9;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_ADDRESS_HIDING_NOT_SUPPORTED = 0xEA;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_LACK_OF_PREPAID = 0xEB;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_END = 0xFF;
+
+ /**
+ * X-Mms-Retrieve-Status field types.
+ */
+ public static final int RETRIEVE_STATUS_OK = 0x80;
+ public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE = 0xC0;
+ public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND = 0xC1;
+ public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM = 0xC2;
+ public static final int RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE = 0xE0;
+ public static final int RETRIEVE_STATUS_ERROR_PERMANENT_SERVICE_DENIED = 0xE1;
+ public static final int RETRIEVE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND = 0xE2;
+ public static final int RETRIEVE_STATUS_ERROR_PERMANENT_CONTENT_UNSUPPORTED = 0xE3;
+ public static final int RETRIEVE_STATUS_ERROR_END = 0xFF;
+
+ /**
+ * X-Mms-Sender-Visibility field types.
+ */
+ public static final int SENDER_VISIBILITY_HIDE = 0x80;
+ public static final int SENDER_VISIBILITY_SHOW = 0x81;
+
+ /**
+ * X-Mms-Read-Status field types.
+ */
+ public static final int READ_STATUS_READ = 0x80;
+ public static final int READ_STATUS__DELETED_WITHOUT_BEING_READ = 0x81;
+
+ /**
+ * X-Mms-Cancel-Status field types.
+ */
+ public static final int CANCEL_STATUS_REQUEST_SUCCESSFULLY_RECEIVED = 0x80;
+ public static final int CANCEL_STATUS_REQUEST_CORRUPTED = 0x81;
+
+ /**
+ * X-Mms-Reply-Charging field types.
+ */
+ public static final int REPLY_CHARGING_REQUESTED = 0x80;
+ public static final int REPLY_CHARGING_REQUESTED_TEXT_ONLY = 0x81;
+ public static final int REPLY_CHARGING_ACCEPTED = 0x82;
+ public static final int REPLY_CHARGING_ACCEPTED_TEXT_ONLY = 0x83;
+
+ /**
+ * X-Mms-MM-State field types.
+ */
+ public static final int MM_STATE_DRAFT = 0x80;
+ public static final int MM_STATE_SENT = 0x81;
+ public static final int MM_STATE_NEW = 0x82;
+ public static final int MM_STATE_RETRIEVED = 0x83;
+ public static final int MM_STATE_FORWARDED = 0x84;
+
+ /**
+ * X-Mms-Recommended-Retrieval-Mode field types.
+ */
+ public static final int RECOMMENDED_RETRIEVAL_MODE_MANUAL = 0x80;
+
+ /**
+ * X-Mms-Content-Class field types.
+ */
+ public static final int CONTENT_CLASS_TEXT = 0x80;
+ public static final int CONTENT_CLASS_IMAGE_BASIC = 0x81;
+ public static final int CONTENT_CLASS_IMAGE_RICH = 0x82;
+ public static final int CONTENT_CLASS_VIDEO_BASIC = 0x83;
+ public static final int CONTENT_CLASS_VIDEO_RICH = 0x84;
+ public static final int CONTENT_CLASS_MEGAPIXEL = 0x85;
+ public static final int CONTENT_CLASS_CONTENT_BASIC = 0x86;
+ public static final int CONTENT_CLASS_CONTENT_RICH = 0x87;
+
+ /**
+ * X-Mms-Store-Status field types.
+ */
+ public static final int STORE_STATUS_SUCCESS = 0x80;
+ public static final int STORE_STATUS_ERROR_TRANSIENT_FAILURE = 0xC0;
+ public static final int STORE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM = 0xC1;
+ public static final int STORE_STATUS_ERROR_PERMANENT_FAILURE = 0xE0;
+ public static final int STORE_STATUS_ERROR_PERMANENT_SERVICE_DENIED = 0xE1;
+ public static final int STORE_STATUS_ERROR_PERMANENT_MESSAGE_FORMAT_CORRUPT = 0xE2;
+ public static final int STORE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND = 0xE3;
+ public static final int STORE_STATUS_ERROR_PERMANENT_MMBOX_FULL = 0xE4;
+ public static final int STORE_STATUS_ERROR_END = 0xFF;
+
+ /**
+ * The map contains the value of all headers.
+ */
+ private HashMap<Integer, Object> mHeaderMap = null;
+
+ /**
+ * Constructor of PduHeaders.
+ */
+ @UnsupportedAppUsage
+ public PduHeaders() {
+ mHeaderMap = new HashMap<Integer, Object>();
+ }
+
+ /**
+ * Get octet value by header field.
+ *
+ * @param field the field
+ * @return the octet value of the pdu header
+ * with specified header field. Return 0 if
+ * the value is not set.
+ */
+ @UnsupportedAppUsage
+ protected int getOctet(int field) {
+ Integer octet = (Integer) mHeaderMap.get(field);
+ if (null == octet) {
+ return 0;
+ }
+
+ return octet;
+ }
+
+ /**
+ * Set octet value to pdu header by header field.
+ *
+ * @param value the value
+ * @param field the field
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ @UnsupportedAppUsage
+ protected void setOctet(int value, int field)
+ throws InvalidHeaderValueException{
+ /**
+ * Check whether this field can be set for specific
+ * header and check validity of the field.
+ */
+ switch (field) {
+ case REPORT_ALLOWED:
+ case ADAPTATION_ALLOWED:
+ case DELIVERY_REPORT:
+ case DRM_CONTENT:
+ case DISTRIBUTION_INDICATOR:
+ case QUOTAS:
+ case READ_REPORT:
+ case STORE:
+ case STORED:
+ case TOTALS:
+ case SENDER_VISIBILITY:
+ if ((VALUE_YES != value) && (VALUE_NO != value)) {
+ // Invalid value.
+ throw new InvalidHeaderValueException("Invalid Octet value!");
+ }
+ break;
+ case READ_STATUS:
+ if ((READ_STATUS_READ != value) &&
+ (READ_STATUS__DELETED_WITHOUT_BEING_READ != value)) {
+ // Invalid value.
+ throw new InvalidHeaderValueException("Invalid Octet value!");
+ }
+ break;
+ case CANCEL_STATUS:
+ if ((CANCEL_STATUS_REQUEST_SUCCESSFULLY_RECEIVED != value) &&
+ (CANCEL_STATUS_REQUEST_CORRUPTED != value)) {
+ // Invalid value.
+ throw new InvalidHeaderValueException("Invalid Octet value!");
+ }
+ break;
+ case PRIORITY:
+ if ((value < PRIORITY_LOW) || (value > PRIORITY_HIGH)) {
+ // Invalid value.
+ throw new InvalidHeaderValueException("Invalid Octet value!");
+ }
+ break;
+ case STATUS:
+ if ((value < STATUS_EXPIRED) || (value > STATUS_UNREACHABLE)) {
+ // Invalid value.
+ throw new InvalidHeaderValueException("Invalid Octet value!");
+ }
+ break;
+ case REPLY_CHARGING:
+ if ((value < REPLY_CHARGING_REQUESTED)
+ || (value > REPLY_CHARGING_ACCEPTED_TEXT_ONLY)) {
+ // Invalid value.
+ throw new InvalidHeaderValueException("Invalid Octet value!");
+ }
+ break;
+ case MM_STATE:
+ if ((value < MM_STATE_DRAFT) || (value > MM_STATE_FORWARDED)) {
+ // Invalid value.
+ throw new InvalidHeaderValueException("Invalid Octet value!");
+ }
+ break;
+ case RECOMMENDED_RETRIEVAL_MODE:
+ if (RECOMMENDED_RETRIEVAL_MODE_MANUAL != value) {
+ // Invalid value.
+ throw new InvalidHeaderValueException("Invalid Octet value!");
+ }
+ break;
+ case CONTENT_CLASS:
+ if ((value < CONTENT_CLASS_TEXT)
+ || (value > CONTENT_CLASS_CONTENT_RICH)) {
+ // Invalid value.
+ throw new InvalidHeaderValueException("Invalid Octet value!");
+ }
+ break;
+ case RETRIEVE_STATUS:
+ // According to oma-ts-mms-enc-v1_3, section 7.3.50, we modify the invalid value.
+ if ((value > RETRIEVE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM) &&
+ (value < RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE)) {
+ value = RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE;
+ } else if ((value > RETRIEVE_STATUS_ERROR_PERMANENT_CONTENT_UNSUPPORTED) &&
+ (value <= RETRIEVE_STATUS_ERROR_END)) {
+ value = RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE;
+ } else if ((value < RETRIEVE_STATUS_OK) ||
+ ((value > RETRIEVE_STATUS_OK) &&
+ (value < RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE)) ||
+ (value > RETRIEVE_STATUS_ERROR_END)) {
+ value = RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE;
+ }
+ break;
+ case STORE_STATUS:
+ // According to oma-ts-mms-enc-v1_3, section 7.3.58, we modify the invalid value.
+ if ((value > STORE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM) &&
+ (value < STORE_STATUS_ERROR_PERMANENT_FAILURE)) {
+ value = STORE_STATUS_ERROR_TRANSIENT_FAILURE;
+ } else if ((value > STORE_STATUS_ERROR_PERMANENT_MMBOX_FULL) &&
+ (value <= STORE_STATUS_ERROR_END)) {
+ value = STORE_STATUS_ERROR_PERMANENT_FAILURE;
+ } else if ((value < STORE_STATUS_SUCCESS) ||
+ ((value > STORE_STATUS_SUCCESS) &&
+ (value < STORE_STATUS_ERROR_TRANSIENT_FAILURE)) ||
+ (value > STORE_STATUS_ERROR_END)) {
+ value = STORE_STATUS_ERROR_PERMANENT_FAILURE;
+ }
+ break;
+ case RESPONSE_STATUS:
+ // According to oma-ts-mms-enc-v1_3, section 7.3.48, we modify the invalid value.
+ if ((value > RESPONSE_STATUS_ERROR_TRANSIENT_PARTIAL_SUCCESS) &&
+ (value < RESPONSE_STATUS_ERROR_PERMANENT_FAILURE)) {
+ value = RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE;
+ } else if (((value > RESPONSE_STATUS_ERROR_PERMANENT_LACK_OF_PREPAID) &&
+ (value <= RESPONSE_STATUS_ERROR_PERMANENT_END)) ||
+ (value < RESPONSE_STATUS_OK) ||
+ ((value > RESPONSE_STATUS_ERROR_UNSUPPORTED_MESSAGE) &&
+ (value < RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE)) ||
+ (value > RESPONSE_STATUS_ERROR_PERMANENT_END)) {
+ value = RESPONSE_STATUS_ERROR_PERMANENT_FAILURE;
+ }
+ break;
+ case MMS_VERSION:
+ if ((value < MMS_VERSION_1_0)|| (value > MMS_VERSION_1_3)) {
+ value = CURRENT_MMS_VERSION; // Current version is the default value.
+ }
+ break;
+ case MESSAGE_TYPE:
+ if ((value < MESSAGE_TYPE_SEND_REQ) || (value > MESSAGE_TYPE_CANCEL_CONF)) {
+ // Invalid value.
+ throw new InvalidHeaderValueException("Invalid Octet value!");
+ }
+ break;
+ default:
+ // This header value should not be Octect.
+ throw new RuntimeException("Invalid header field!");
+ }
+ mHeaderMap.put(field, value);
+ }
+
+ /**
+ * Get TextString value by header field.
+ *
+ * @param field the field
+ * @return the TextString value of the pdu header
+ * with specified header field
+ */
+ @UnsupportedAppUsage
+ protected byte[] getTextString(int field) {
+ return (byte[]) mHeaderMap.get(field);
+ }
+
+ /**
+ * Set TextString value to pdu header by header field.
+ *
+ * @param value the value
+ * @param field the field
+ * @return the TextString value of the pdu header
+ * with specified header field
+ * @throws NullPointerException if the value is null.
+ */
+ protected void setTextString(byte[] value, int field) {
+ /**
+ * Check whether this field can be set for specific
+ * header and check validity of the field.
+ */
+ if (null == value) {
+ throw new NullPointerException();
+ }
+
+ switch (field) {
+ case TRANSACTION_ID:
+ case REPLY_CHARGING_ID:
+ case AUX_APPLIC_ID:
+ case APPLIC_ID:
+ case REPLY_APPLIC_ID:
+ case MESSAGE_ID:
+ case REPLACE_ID:
+ case CANCEL_ID:
+ case CONTENT_LOCATION:
+ case MESSAGE_CLASS:
+ case CONTENT_TYPE:
+ break;
+ default:
+ // This header value should not be Text-String.
+ throw new RuntimeException("Invalid header field!");
+ }
+ mHeaderMap.put(field, value);
+ }
+
+ /**
+ * Get EncodedStringValue value by header field.
+ *
+ * @param field the field
+ * @return the EncodedStringValue value of the pdu header
+ * with specified header field
+ */
+ @UnsupportedAppUsage
+ protected EncodedStringValue getEncodedStringValue(int field) {
+ return (EncodedStringValue) mHeaderMap.get(field);
+ }
+
+ /**
+ * Get TO, CC or BCC header value.
+ *
+ * @param field the field
+ * @return the EncodeStringValue array of the pdu header
+ * with specified header field
+ */
+ @UnsupportedAppUsage
+ protected EncodedStringValue[] getEncodedStringValues(int field) {
+ ArrayList<EncodedStringValue> list =
+ (ArrayList<EncodedStringValue>) mHeaderMap.get(field);
+ if (null == list) {
+ return null;
+ }
+ EncodedStringValue[] values = new EncodedStringValue[list.size()];
+ return list.toArray(values);
+ }
+
+ /**
+ * Set EncodedStringValue value to pdu header by header field.
+ *
+ * @param value the value
+ * @param field the field
+ * @return the EncodedStringValue value of the pdu header
+ * with specified header field
+ * @throws NullPointerException if the value is null.
+ */
+ @UnsupportedAppUsage
+ protected void setEncodedStringValue(EncodedStringValue value, int field) {
+ /**
+ * Check whether this field can be set for specific
+ * header and check validity of the field.
+ */
+ if (null == value) {
+ throw new NullPointerException();
+ }
+
+ switch (field) {
+ case SUBJECT:
+ case RECOMMENDED_RETRIEVAL_MODE_TEXT:
+ case RETRIEVE_TEXT:
+ case STATUS_TEXT:
+ case STORE_STATUS_TEXT:
+ case RESPONSE_TEXT:
+ case FROM:
+ case PREVIOUSLY_SENT_BY:
+ case MM_FLAGS:
+ break;
+ default:
+ // This header value should not be Encoded-String-Value.
+ throw new RuntimeException("Invalid header field!");
+ }
+
+ mHeaderMap.put(field, value);
+ }
+
+ /**
+ * Set TO, CC or BCC header value.
+ *
+ * @param value the value
+ * @param field the field
+ * @return the EncodedStringValue value array of the pdu header
+ * with specified header field
+ * @throws NullPointerException if the value is null.
+ */
+ protected void setEncodedStringValues(EncodedStringValue[] value, int field) {
+ /**
+ * Check whether this field can be set for specific
+ * header and check validity of the field.
+ */
+ if (null == value) {
+ throw new NullPointerException();
+ }
+
+ switch (field) {
+ case BCC:
+ case CC:
+ case TO:
+ break;
+ default:
+ // This header value should not be Encoded-String-Value.
+ throw new RuntimeException("Invalid header field!");
+ }
+
+ ArrayList<EncodedStringValue> list = new ArrayList<EncodedStringValue>();
+ for (int i = 0; i < value.length; i++) {
+ list.add(value[i]);
+ }
+ mHeaderMap.put(field, list);
+ }
+
+ /**
+ * Append one EncodedStringValue to another.
+ *
+ * @param value the EncodedStringValue to append
+ * @param field the field
+ * @throws NullPointerException if the value is null.
+ */
+ @UnsupportedAppUsage
+ protected void appendEncodedStringValue(EncodedStringValue value,
+ int field) {
+ if (null == value) {
+ throw new NullPointerException();
+ }
+
+ switch (field) {
+ case BCC:
+ case CC:
+ case TO:
+ break;
+ default:
+ throw new RuntimeException("Invalid header field!");
+ }
+
+ ArrayList<EncodedStringValue> list =
+ (ArrayList<EncodedStringValue>) mHeaderMap.get(field);
+ if (null == list) {
+ list = new ArrayList<EncodedStringValue>();
+ }
+ list.add(value);
+ mHeaderMap.put(field, list);
+ }
+
+ /**
+ * Get LongInteger value by header field.
+ *
+ * @param field the field
+ * @return the LongInteger value of the pdu header
+ * with specified header field. if return -1, the
+ * field is not existed in pdu header.
+ */
+ @UnsupportedAppUsage
+ protected long getLongInteger(int field) {
+ Long longInteger = (Long) mHeaderMap.get(field);
+ if (null == longInteger) {
+ return -1;
+ }
+
+ return longInteger.longValue();
+ }
+
+ /**
+ * Set LongInteger value to pdu header by header field.
+ *
+ * @param value the value
+ * @param field the field
+ */
+ @UnsupportedAppUsage
+ protected void setLongInteger(long value, int field) {
+ /**
+ * Check whether this field can be set for specific
+ * header and check validity of the field.
+ */
+ switch (field) {
+ case DATE:
+ case REPLY_CHARGING_SIZE:
+ case MESSAGE_SIZE:
+ case MESSAGE_COUNT:
+ case START:
+ case LIMIT:
+ case DELIVERY_TIME:
+ case EXPIRY:
+ case REPLY_CHARGING_DEADLINE:
+ case PREVIOUSLY_SENT_DATE:
+ break;
+ default:
+ // This header value should not be LongInteger.
+ throw new RuntimeException("Invalid header field!");
+ }
+ mHeaderMap.put(field, value);
+ }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/PduParser.java b/telephony/java/com/google/android/mms/pdu/PduParser.java
new file mode 100755
index 0000000..f483994
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/PduParser.java
@@ -0,0 +1,2023 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-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.google.android.mms.pdu;
+
+import android.util.Log;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.ContentType;
+import com.google.android.mms.InvalidHeaderValueException;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+import java.util.HashMap;
+
+public class PduParser {
+ /**
+ * The next are WAP values defined in WSP specification.
+ */
+ private static final int QUOTE = 127;
+ private static final int LENGTH_QUOTE = 31;
+ private static final int TEXT_MIN = 32;
+ private static final int TEXT_MAX = 127;
+ private static final int SHORT_INTEGER_MAX = 127;
+ private static final int SHORT_LENGTH_MAX = 30;
+ private static final int LONG_INTEGER_LENGTH_MAX = 8;
+ private static final int QUOTED_STRING_FLAG = 34;
+ private static final int END_STRING_FLAG = 0x00;
+ //The next two are used by the interface "parseWapString" to
+ //distinguish Text-String and Quoted-String.
+ private static final int TYPE_TEXT_STRING = 0;
+ private static final int TYPE_QUOTED_STRING = 1;
+ private static final int TYPE_TOKEN_STRING = 2;
+
+ /**
+ * Specify the part position.
+ */
+ private static final int THE_FIRST_PART = 0;
+ private static final int THE_LAST_PART = 1;
+
+ /**
+ * The pdu data.
+ */
+ private ByteArrayInputStream mPduDataStream = null;
+
+ /**
+ * Store pdu headers
+ */
+ private PduHeaders mHeaders = null;
+
+ /**
+ * Store pdu parts.
+ */
+ private PduBody mBody = null;
+
+ /**
+ * Store the "type" parameter in "Content-Type" header field.
+ */
+ private static byte[] mTypeParam = null;
+
+ /**
+ * Store the "start" parameter in "Content-Type" header field.
+ */
+ private static byte[] mStartParam = null;
+
+ /**
+ * The log tag.
+ */
+ private static final String LOG_TAG = "PduParser";
+ private static final boolean DEBUG = false;
+ private static final boolean LOCAL_LOGV = false;
+
+ /**
+ * Whether to parse content-disposition part header
+ */
+ private final boolean mParseContentDisposition;
+
+ /**
+ * Constructor.
+ *
+ * @param pduDataStream pdu data to be parsed
+ * @param parseContentDisposition whether to parse the Content-Disposition part header
+ */
+ @UnsupportedAppUsage
+ public PduParser(byte[] pduDataStream, boolean parseContentDisposition) {
+ mPduDataStream = new ByteArrayInputStream(pduDataStream);
+ mParseContentDisposition = parseContentDisposition;
+ }
+
+ /**
+ * Parse the pdu.
+ *
+ * @return the pdu structure if parsing successfully.
+ * null if parsing error happened or mandatory fields are not set.
+ */
+ @UnsupportedAppUsage
+ public GenericPdu parse(){
+ if (mPduDataStream == null) {
+ return null;
+ }
+
+ /* parse headers */
+ mHeaders = parseHeaders(mPduDataStream);
+ if (null == mHeaders) {
+ // Parse headers failed.
+ return null;
+ }
+
+ /* get the message type */
+ int messageType = mHeaders.getOctet(PduHeaders.MESSAGE_TYPE);
+
+ /* check mandatory header fields */
+ if (false == checkMandatoryHeader(mHeaders)) {
+ log("check mandatory headers failed!");
+ return null;
+ }
+
+ if ((PduHeaders.MESSAGE_TYPE_SEND_REQ == messageType) ||
+ (PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF == messageType)) {
+ /* need to parse the parts */
+ mBody = parseParts(mPduDataStream);
+ if (null == mBody) {
+ // Parse parts failed.
+ return null;
+ }
+ }
+
+ switch (messageType) {
+ case PduHeaders.MESSAGE_TYPE_SEND_REQ:
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parse: MESSAGE_TYPE_SEND_REQ");
+ }
+ SendReq sendReq = new SendReq(mHeaders, mBody);
+ return sendReq;
+ case PduHeaders.MESSAGE_TYPE_SEND_CONF:
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parse: MESSAGE_TYPE_SEND_CONF");
+ }
+ SendConf sendConf = new SendConf(mHeaders);
+ return sendConf;
+ case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parse: MESSAGE_TYPE_NOTIFICATION_IND");
+ }
+ NotificationInd notificationInd =
+ new NotificationInd(mHeaders);
+ return notificationInd;
+ case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parse: MESSAGE_TYPE_NOTIFYRESP_IND");
+ }
+ NotifyRespInd notifyRespInd =
+ new NotifyRespInd(mHeaders);
+ return notifyRespInd;
+ case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parse: MESSAGE_TYPE_RETRIEVE_CONF");
+ }
+ RetrieveConf retrieveConf =
+ new RetrieveConf(mHeaders, mBody);
+
+ byte[] contentType = retrieveConf.getContentType();
+ if (null == contentType) {
+ return null;
+ }
+ String ctTypeStr = new String(contentType);
+ if (ctTypeStr.equals(ContentType.MULTIPART_MIXED)
+ || ctTypeStr.equals(ContentType.MULTIPART_RELATED)
+ || ctTypeStr.equals(ContentType.MULTIPART_ALTERNATIVE)) {
+ // The MMS content type must be "application/vnd.wap.multipart.mixed"
+ // or "application/vnd.wap.multipart.related"
+ // or "application/vnd.wap.multipart.alternative"
+ return retrieveConf;
+ } else if (ctTypeStr.equals(ContentType.MULTIPART_ALTERNATIVE)) {
+ // "application/vnd.wap.multipart.alternative"
+ // should take only the first part.
+ PduPart firstPart = mBody.getPart(0);
+ mBody.removeAll();
+ mBody.addPart(0, firstPart);
+ return retrieveConf;
+ }
+ return null;
+ case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parse: MESSAGE_TYPE_DELIVERY_IND");
+ }
+ DeliveryInd deliveryInd =
+ new DeliveryInd(mHeaders);
+ return deliveryInd;
+ case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parse: MESSAGE_TYPE_ACKNOWLEDGE_IND");
+ }
+ AcknowledgeInd acknowledgeInd =
+ new AcknowledgeInd(mHeaders);
+ return acknowledgeInd;
+ case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parse: MESSAGE_TYPE_READ_ORIG_IND");
+ }
+ ReadOrigInd readOrigInd =
+ new ReadOrigInd(mHeaders);
+ return readOrigInd;
+ case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parse: MESSAGE_TYPE_READ_REC_IND");
+ }
+ ReadRecInd readRecInd =
+ new ReadRecInd(mHeaders);
+ return readRecInd;
+ default:
+ log("Parser doesn't support this message type in this version!");
+ return null;
+ }
+ }
+
+ /**
+ * Parse pdu headers.
+ *
+ * @param pduDataStream pdu data input stream
+ * @return headers in PduHeaders structure, null when parse fail
+ */
+ protected PduHeaders parseHeaders(ByteArrayInputStream pduDataStream){
+ if (pduDataStream == null) {
+ return null;
+ }
+ boolean keepParsing = true;
+ PduHeaders headers = new PduHeaders();
+
+ while (keepParsing && (pduDataStream.available() > 0)) {
+ pduDataStream.mark(1);
+ int headerField = extractByteValue(pduDataStream);
+ /* parse custom text header */
+ if ((headerField >= TEXT_MIN) && (headerField <= TEXT_MAX)) {
+ pduDataStream.reset();
+ byte [] bVal = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "TextHeader: " + new String(bVal));
+ }
+ /* we should ignore it at the moment */
+ continue;
+ }
+ switch (headerField) {
+ case PduHeaders.MESSAGE_TYPE:
+ {
+ int messageType = extractByteValue(pduDataStream);
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: messageType: " + messageType);
+ }
+ switch (messageType) {
+ // We don't support these kind of messages now.
+ case PduHeaders.MESSAGE_TYPE_FORWARD_REQ:
+ case PduHeaders.MESSAGE_TYPE_FORWARD_CONF:
+ case PduHeaders.MESSAGE_TYPE_MBOX_STORE_REQ:
+ case PduHeaders.MESSAGE_TYPE_MBOX_STORE_CONF:
+ case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_REQ:
+ case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_CONF:
+ case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_REQ:
+ case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_CONF:
+ case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_REQ:
+ case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_CONF:
+ case PduHeaders.MESSAGE_TYPE_MBOX_DESCR:
+ case PduHeaders.MESSAGE_TYPE_DELETE_REQ:
+ case PduHeaders.MESSAGE_TYPE_DELETE_CONF:
+ case PduHeaders.MESSAGE_TYPE_CANCEL_REQ:
+ case PduHeaders.MESSAGE_TYPE_CANCEL_CONF:
+ return null;
+ }
+ try {
+ headers.setOctet(messageType, headerField);
+ } catch(InvalidHeaderValueException e) {
+ log("Set invalid Octet value: " + messageType +
+ " into the header filed: " + headerField);
+ return null;
+ } catch(RuntimeException e) {
+ log(headerField + "is not Octet header field!");
+ return null;
+ }
+ break;
+ }
+ /* Octect value */
+ case PduHeaders.REPORT_ALLOWED:
+ case PduHeaders.ADAPTATION_ALLOWED:
+ case PduHeaders.DELIVERY_REPORT:
+ case PduHeaders.DRM_CONTENT:
+ case PduHeaders.DISTRIBUTION_INDICATOR:
+ case PduHeaders.QUOTAS:
+ case PduHeaders.READ_REPORT:
+ case PduHeaders.STORE:
+ case PduHeaders.STORED:
+ case PduHeaders.TOTALS:
+ case PduHeaders.SENDER_VISIBILITY:
+ case PduHeaders.READ_STATUS:
+ case PduHeaders.CANCEL_STATUS:
+ case PduHeaders.PRIORITY:
+ case PduHeaders.STATUS:
+ case PduHeaders.REPLY_CHARGING:
+ case PduHeaders.MM_STATE:
+ case PduHeaders.RECOMMENDED_RETRIEVAL_MODE:
+ case PduHeaders.CONTENT_CLASS:
+ case PduHeaders.RETRIEVE_STATUS:
+ case PduHeaders.STORE_STATUS:
+ /**
+ * The following field has a different value when
+ * used in the M-Mbox-Delete.conf and M-Delete.conf PDU.
+ * For now we ignore this fact, since we do not support these PDUs
+ */
+ case PduHeaders.RESPONSE_STATUS:
+ {
+ int value = extractByteValue(pduDataStream);
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: byte: " + headerField + " value: " +
+ value);
+ }
+
+ try {
+ headers.setOctet(value, headerField);
+ } catch(InvalidHeaderValueException e) {
+ log("Set invalid Octet value: " + value +
+ " into the header filed: " + headerField);
+ return null;
+ } catch(RuntimeException e) {
+ log(headerField + "is not Octet header field!");
+ return null;
+ }
+ break;
+ }
+
+ /* Long-Integer */
+ case PduHeaders.DATE:
+ case PduHeaders.REPLY_CHARGING_SIZE:
+ case PduHeaders.MESSAGE_SIZE:
+ {
+ try {
+ long value = parseLongInteger(pduDataStream);
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: longint: " + headerField + " value: " +
+ value);
+ }
+ headers.setLongInteger(value, headerField);
+ } catch(RuntimeException e) {
+ log(headerField + "is not Long-Integer header field!");
+ return null;
+ }
+ break;
+ }
+
+ /* Integer-Value */
+ case PduHeaders.MESSAGE_COUNT:
+ case PduHeaders.START:
+ case PduHeaders.LIMIT:
+ {
+ try {
+ long value = parseIntegerValue(pduDataStream);
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: int: " + headerField + " value: " +
+ value);
+ }
+ headers.setLongInteger(value, headerField);
+ } catch(RuntimeException e) {
+ log(headerField + "is not Long-Integer header field!");
+ return null;
+ }
+ break;
+ }
+
+ /* Text-String */
+ case PduHeaders.TRANSACTION_ID:
+ case PduHeaders.REPLY_CHARGING_ID:
+ case PduHeaders.AUX_APPLIC_ID:
+ case PduHeaders.APPLIC_ID:
+ case PduHeaders.REPLY_APPLIC_ID:
+ /**
+ * The next three header fields are email addresses
+ * as defined in RFC2822,
+ * not including the characters "<" and ">"
+ */
+ case PduHeaders.MESSAGE_ID:
+ case PduHeaders.REPLACE_ID:
+ case PduHeaders.CANCEL_ID:
+ /**
+ * The following field has a different value when
+ * used in the M-Mbox-Delete.conf and M-Delete.conf PDU.
+ * For now we ignore this fact, since we do not support these PDUs
+ */
+ case PduHeaders.CONTENT_LOCATION:
+ {
+ byte[] value = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ if (null != value) {
+ try {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: string: " + headerField + " value: " +
+ new String(value));
+ }
+ headers.setTextString(value, headerField);
+ } catch(NullPointerException e) {
+ log("null pointer error!");
+ } catch(RuntimeException e) {
+ log(headerField + "is not Text-String header field!");
+ return null;
+ }
+ }
+ break;
+ }
+
+ /* Encoded-string-value */
+ case PduHeaders.SUBJECT:
+ case PduHeaders.RECOMMENDED_RETRIEVAL_MODE_TEXT:
+ case PduHeaders.RETRIEVE_TEXT:
+ case PduHeaders.STATUS_TEXT:
+ case PduHeaders.STORE_STATUS_TEXT:
+ /* the next one is not support
+ * M-Mbox-Delete.conf and M-Delete.conf now */
+ case PduHeaders.RESPONSE_TEXT:
+ {
+ EncodedStringValue value =
+ parseEncodedStringValue(pduDataStream);
+ if (null != value) {
+ try {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: encoded string: " + headerField
+ + " value: " + value.getString());
+ }
+ headers.setEncodedStringValue(value, headerField);
+ } catch(NullPointerException e) {
+ log("null pointer error!");
+ } catch (RuntimeException e) {
+ log(headerField + "is not Encoded-String-Value header field!");
+ return null;
+ }
+ }
+ break;
+ }
+
+ /* Addressing model */
+ case PduHeaders.BCC:
+ case PduHeaders.CC:
+ case PduHeaders.TO:
+ {
+ EncodedStringValue value =
+ parseEncodedStringValue(pduDataStream);
+ if (null != value) {
+ byte[] address = value.getTextString();
+ if (null != address) {
+ String str = new String(address);
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: (to/cc/bcc) address: " + headerField
+ + " value: " + str);
+ }
+ int endIndex = str.indexOf("/");
+ if (endIndex > 0) {
+ str = str.substring(0, endIndex);
+ }
+ try {
+ value.setTextString(str.getBytes());
+ } catch(NullPointerException e) {
+ log("null pointer error!");
+ return null;
+ }
+ }
+
+ try {
+ headers.appendEncodedStringValue(value, headerField);
+ } catch(NullPointerException e) {
+ log("null pointer error!");
+ } catch(RuntimeException e) {
+ log(headerField + "is not Encoded-String-Value header field!");
+ return null;
+ }
+ }
+ break;
+ }
+
+ /* Value-length
+ * (Absolute-token Date-value | Relative-token Delta-seconds-value) */
+ case PduHeaders.DELIVERY_TIME:
+ case PduHeaders.EXPIRY:
+ case PduHeaders.REPLY_CHARGING_DEADLINE:
+ {
+ /* parse Value-length */
+ parseValueLength(pduDataStream);
+
+ /* Absolute-token or Relative-token */
+ int token = extractByteValue(pduDataStream);
+
+ /* Date-value or Delta-seconds-value */
+ long timeValue;
+ try {
+ timeValue = parseLongInteger(pduDataStream);
+ } catch(RuntimeException e) {
+ log(headerField + "is not Long-Integer header field!");
+ return null;
+ }
+ if (PduHeaders.VALUE_RELATIVE_TOKEN == token) {
+ /* need to convert the Delta-seconds-value
+ * into Date-value */
+ timeValue = System.currentTimeMillis()/1000 + timeValue;
+ }
+
+ try {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: time value: " + headerField
+ + " value: " + timeValue);
+ }
+ headers.setLongInteger(timeValue, headerField);
+ } catch(RuntimeException e) {
+ log(headerField + "is not Long-Integer header field!");
+ return null;
+ }
+ break;
+ }
+
+ case PduHeaders.FROM: {
+ /* From-value =
+ * Value-length
+ * (Address-present-token Encoded-string-value | Insert-address-token)
+ */
+ EncodedStringValue from = null;
+ parseValueLength(pduDataStream); /* parse value-length */
+
+ /* Address-present-token or Insert-address-token */
+ int fromToken = extractByteValue(pduDataStream);
+
+ /* Address-present-token or Insert-address-token */
+ if (PduHeaders.FROM_ADDRESS_PRESENT_TOKEN == fromToken) {
+ /* Encoded-string-value */
+ from = parseEncodedStringValue(pduDataStream);
+ if (null != from) {
+ byte[] address = from.getTextString();
+ if (null != address) {
+ String str = new String(address);
+ int endIndex = str.indexOf("/");
+ if (endIndex > 0) {
+ str = str.substring(0, endIndex);
+ }
+ try {
+ from.setTextString(str.getBytes());
+ } catch(NullPointerException e) {
+ log("null pointer error!");
+ return null;
+ }
+ }
+ }
+ } else {
+ try {
+ from = new EncodedStringValue(
+ PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR.getBytes());
+ } catch(NullPointerException e) {
+ log(headerField + "is not Encoded-String-Value header field!");
+ return null;
+ }
+ }
+
+ try {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: from address: " + headerField
+ + " value: " + from.getString());
+ }
+ headers.setEncodedStringValue(from, PduHeaders.FROM);
+ } catch(NullPointerException e) {
+ log("null pointer error!");
+ } catch(RuntimeException e) {
+ log(headerField + "is not Encoded-String-Value header field!");
+ return null;
+ }
+ break;
+ }
+
+ case PduHeaders.MESSAGE_CLASS: {
+ /* Message-class-value = Class-identifier | Token-text */
+ pduDataStream.mark(1);
+ int messageClass = extractByteValue(pduDataStream);
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: MESSAGE_CLASS: " + headerField
+ + " value: " + messageClass);
+ }
+
+ if (messageClass >= PduHeaders.MESSAGE_CLASS_PERSONAL) {
+ /* Class-identifier */
+ try {
+ if (PduHeaders.MESSAGE_CLASS_PERSONAL == messageClass) {
+ headers.setTextString(
+ PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes(),
+ PduHeaders.MESSAGE_CLASS);
+ } else if (PduHeaders.MESSAGE_CLASS_ADVERTISEMENT == messageClass) {
+ headers.setTextString(
+ PduHeaders.MESSAGE_CLASS_ADVERTISEMENT_STR.getBytes(),
+ PduHeaders.MESSAGE_CLASS);
+ } else if (PduHeaders.MESSAGE_CLASS_INFORMATIONAL == messageClass) {
+ headers.setTextString(
+ PduHeaders.MESSAGE_CLASS_INFORMATIONAL_STR.getBytes(),
+ PduHeaders.MESSAGE_CLASS);
+ } else if (PduHeaders.MESSAGE_CLASS_AUTO == messageClass) {
+ headers.setTextString(
+ PduHeaders.MESSAGE_CLASS_AUTO_STR.getBytes(),
+ PduHeaders.MESSAGE_CLASS);
+ }
+ } catch(NullPointerException e) {
+ log("null pointer error!");
+ } catch(RuntimeException e) {
+ log(headerField + "is not Text-String header field!");
+ return null;
+ }
+ } else {
+ /* Token-text */
+ pduDataStream.reset();
+ byte[] messageClassString = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ if (null != messageClassString) {
+ try {
+ headers.setTextString(messageClassString, PduHeaders.MESSAGE_CLASS);
+ } catch(NullPointerException e) {
+ log("null pointer error!");
+ } catch(RuntimeException e) {
+ log(headerField + "is not Text-String header field!");
+ return null;
+ }
+ }
+ }
+ break;
+ }
+
+ case PduHeaders.MMS_VERSION: {
+ int version = parseShortInteger(pduDataStream);
+
+ try {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: MMS_VERSION: " + headerField
+ + " value: " + version);
+ }
+ headers.setOctet(version, PduHeaders.MMS_VERSION);
+ } catch(InvalidHeaderValueException e) {
+ log("Set invalid Octet value: " + version +
+ " into the header filed: " + headerField);
+ return null;
+ } catch(RuntimeException e) {
+ log(headerField + "is not Octet header field!");
+ return null;
+ }
+ break;
+ }
+
+ case PduHeaders.PREVIOUSLY_SENT_BY: {
+ /* Previously-sent-by-value =
+ * Value-length Forwarded-count-value Encoded-string-value */
+ /* parse value-length */
+ parseValueLength(pduDataStream);
+
+ /* parse Forwarded-count-value */
+ try {
+ parseIntegerValue(pduDataStream);
+ } catch(RuntimeException e) {
+ log(headerField + " is not Integer-Value");
+ return null;
+ }
+
+ /* parse Encoded-string-value */
+ EncodedStringValue previouslySentBy =
+ parseEncodedStringValue(pduDataStream);
+ if (null != previouslySentBy) {
+ try {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: PREVIOUSLY_SENT_BY: " + headerField
+ + " value: " + previouslySentBy.getString());
+ }
+ headers.setEncodedStringValue(previouslySentBy,
+ PduHeaders.PREVIOUSLY_SENT_BY);
+ } catch(NullPointerException e) {
+ log("null pointer error!");
+ } catch(RuntimeException e) {
+ log(headerField + "is not Encoded-String-Value header field!");
+ return null;
+ }
+ }
+ break;
+ }
+
+ case PduHeaders.PREVIOUSLY_SENT_DATE: {
+ /* Previously-sent-date-value =
+ * Value-length Forwarded-count-value Date-value */
+ /* parse value-length */
+ parseValueLength(pduDataStream);
+
+ /* parse Forwarded-count-value */
+ try {
+ parseIntegerValue(pduDataStream);
+ } catch(RuntimeException e) {
+ log(headerField + " is not Integer-Value");
+ return null;
+ }
+
+ /* Date-value */
+ try {
+ long perviouslySentDate = parseLongInteger(pduDataStream);
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: PREVIOUSLY_SENT_DATE: " + headerField
+ + " value: " + perviouslySentDate);
+ }
+ headers.setLongInteger(perviouslySentDate,
+ PduHeaders.PREVIOUSLY_SENT_DATE);
+ } catch(RuntimeException e) {
+ log(headerField + "is not Long-Integer header field!");
+ return null;
+ }
+ break;
+ }
+
+ case PduHeaders.MM_FLAGS: {
+ /* MM-flags-value =
+ * Value-length
+ * ( Add-token | Remove-token | Filter-token )
+ * Encoded-string-value
+ */
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: MM_FLAGS: " + headerField
+ + " NOT REALLY SUPPORTED");
+ }
+
+ /* parse Value-length */
+ parseValueLength(pduDataStream);
+
+ /* Add-token | Remove-token | Filter-token */
+ extractByteValue(pduDataStream);
+
+ /* Encoded-string-value */
+ parseEncodedStringValue(pduDataStream);
+
+ /* not store this header filed in "headers",
+ * because now PduHeaders doesn't support it */
+ break;
+ }
+
+ /* Value-length
+ * (Message-total-token | Size-total-token) Integer-Value */
+ case PduHeaders.MBOX_TOTALS:
+ case PduHeaders.MBOX_QUOTAS:
+ {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: MBOX_TOTALS: " + headerField);
+ }
+ /* Value-length */
+ parseValueLength(pduDataStream);
+
+ /* Message-total-token | Size-total-token */
+ extractByteValue(pduDataStream);
+
+ /*Integer-Value*/
+ try {
+ parseIntegerValue(pduDataStream);
+ } catch(RuntimeException e) {
+ log(headerField + " is not Integer-Value");
+ return null;
+ }
+
+ /* not store these headers filed in "headers",
+ because now PduHeaders doesn't support them */
+ break;
+ }
+
+ case PduHeaders.ELEMENT_DESCRIPTOR: {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: ELEMENT_DESCRIPTOR: " + headerField);
+ }
+ parseContentType(pduDataStream, null);
+
+ /* not store this header filed in "headers",
+ because now PduHeaders doesn't support it */
+ break;
+ }
+
+ case PduHeaders.CONTENT_TYPE: {
+ HashMap<Integer, Object> map =
+ new HashMap<Integer, Object>();
+ byte[] contentType =
+ parseContentType(pduDataStream, map);
+
+ if (null != contentType) {
+ try {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: CONTENT_TYPE: " + headerField +
+ contentType.toString());
+ }
+ headers.setTextString(contentType, PduHeaders.CONTENT_TYPE);
+ } catch(NullPointerException e) {
+ log("null pointer error!");
+ } catch(RuntimeException e) {
+ log(headerField + "is not Text-String header field!");
+ return null;
+ }
+ }
+
+ /* get start parameter */
+ mStartParam = (byte[]) map.get(PduPart.P_START);
+
+ /* get charset parameter */
+ mTypeParam= (byte[]) map.get(PduPart.P_TYPE);
+
+ keepParsing = false;
+ break;
+ }
+
+ case PduHeaders.CONTENT:
+ case PduHeaders.ADDITIONAL_HEADERS:
+ case PduHeaders.ATTRIBUTES:
+ default: {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: Unknown header: " + headerField);
+ }
+ log("Unknown header");
+ }
+ }
+ }
+
+ return headers;
+ }
+
+ /**
+ * Parse pdu parts.
+ *
+ * @param pduDataStream pdu data input stream
+ * @return parts in PduBody structure
+ */
+ protected PduBody parseParts(ByteArrayInputStream pduDataStream) {
+ if (pduDataStream == null) {
+ return null;
+ }
+
+ int count = parseUnsignedInt(pduDataStream); // get the number of parts
+ PduBody body = new PduBody();
+
+ for (int i = 0 ; i < count ; i++) {
+ int headerLength = parseUnsignedInt(pduDataStream);
+ int dataLength = parseUnsignedInt(pduDataStream);
+ PduPart part = new PduPart();
+ int startPos = pduDataStream.available();
+ if (startPos <= 0) {
+ // Invalid part.
+ return null;
+ }
+
+ /* parse part's content-type */
+ HashMap<Integer, Object> map = new HashMap<Integer, Object>();
+ byte[] contentType = parseContentType(pduDataStream, map);
+ if (null != contentType) {
+ part.setContentType(contentType);
+ } else {
+ part.setContentType((PduContentTypes.contentTypes[0]).getBytes()); //"*/*"
+ }
+
+ /* get name parameter */
+ byte[] name = (byte[]) map.get(PduPart.P_NAME);
+ if (null != name) {
+ part.setName(name);
+ }
+
+ /* get charset parameter */
+ Integer charset = (Integer) map.get(PduPart.P_CHARSET);
+ if (null != charset) {
+ part.setCharset(charset);
+ }
+
+ /* parse part's headers */
+ int endPos = pduDataStream.available();
+ int partHeaderLen = headerLength - (startPos - endPos);
+ if (partHeaderLen > 0) {
+ if (false == parsePartHeaders(pduDataStream, part, partHeaderLen)) {
+ // Parse part header faild.
+ return null;
+ }
+ } else if (partHeaderLen < 0) {
+ // Invalid length of content-type.
+ return null;
+ }
+
+ /* FIXME: check content-id, name, filename and content location,
+ * if not set anyone of them, generate a default content-location
+ */
+ if ((null == part.getContentLocation())
+ && (null == part.getName())
+ && (null == part.getFilename())
+ && (null == part.getContentId())) {
+ part.setContentLocation(Long.toOctalString(
+ System.currentTimeMillis()).getBytes());
+ }
+
+ /* get part's data */
+ if (dataLength > 0) {
+ byte[] partData = new byte[dataLength];
+ String partContentType = new String(part.getContentType());
+ pduDataStream.read(partData, 0, dataLength);
+ if (partContentType.equalsIgnoreCase(ContentType.MULTIPART_ALTERNATIVE)) {
+ // parse "multipart/vnd.wap.multipart.alternative".
+ PduBody childBody = parseParts(new ByteArrayInputStream(partData));
+ // take the first part of children.
+ part = childBody.getPart(0);
+ } else {
+ // Check Content-Transfer-Encoding.
+ byte[] partDataEncoding = part.getContentTransferEncoding();
+ if (null != partDataEncoding) {
+ String encoding = new String(partDataEncoding);
+ if (encoding.equalsIgnoreCase(PduPart.P_BASE64)) {
+ // Decode "base64" into "binary".
+ partData = Base64.decodeBase64(partData);
+ } else if (encoding.equalsIgnoreCase(PduPart.P_QUOTED_PRINTABLE)) {
+ // Decode "quoted-printable" into "binary".
+ partData = QuotedPrintable.decodeQuotedPrintable(partData);
+ } else {
+ // "binary" is the default encoding.
+ }
+ }
+ if (null == partData) {
+ log("Decode part data error!");
+ return null;
+ }
+ part.setData(partData);
+ }
+ }
+
+ /* add this part to body */
+ if (THE_FIRST_PART == checkPartPosition(part)) {
+ /* this is the first part */
+ body.addPart(0, part);
+ } else {
+ /* add the part to the end */
+ body.addPart(part);
+ }
+ }
+
+ return body;
+ }
+
+ /**
+ * Log status.
+ *
+ * @param text log information
+ */
+ @UnsupportedAppUsage
+ private static void log(String text) {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, text);
+ }
+ }
+
+ /**
+ * Parse unsigned integer.
+ *
+ * @param pduDataStream pdu data input stream
+ * @return the integer, -1 when failed
+ */
+ @UnsupportedAppUsage
+ protected static int parseUnsignedInt(ByteArrayInputStream pduDataStream) {
+ /**
+ * From wap-230-wsp-20010705-a.pdf
+ * The maximum size of a uintvar is 32 bits.
+ * So it will be encoded in no more than 5 octets.
+ */
+ assert(null != pduDataStream);
+ int result = 0;
+ int temp = pduDataStream.read();
+ if (temp == -1) {
+ return temp;
+ }
+
+ while((temp & 0x80) != 0) {
+ result = result << 7;
+ result |= temp & 0x7F;
+ temp = pduDataStream.read();
+ if (temp == -1) {
+ return temp;
+ }
+ }
+
+ result = result << 7;
+ result |= temp & 0x7F;
+
+ return result;
+ }
+
+ /**
+ * Parse value length.
+ *
+ * @param pduDataStream pdu data input stream
+ * @return the integer
+ */
+ @UnsupportedAppUsage
+ protected static int parseValueLength(ByteArrayInputStream pduDataStream) {
+ /**
+ * From wap-230-wsp-20010705-a.pdf
+ * Value-length = Short-length | (Length-quote Length)
+ * Short-length = <Any octet 0-30>
+ * Length-quote = <Octet 31>
+ * Length = Uintvar-integer
+ * Uintvar-integer = 1*5 OCTET
+ */
+ assert(null != pduDataStream);
+ int temp = pduDataStream.read();
+ assert(-1 != temp);
+ int first = temp & 0xFF;
+
+ if (first <= SHORT_LENGTH_MAX) {
+ return first;
+ } else if (first == LENGTH_QUOTE) {
+ return parseUnsignedInt(pduDataStream);
+ }
+
+ throw new RuntimeException ("Value length > LENGTH_QUOTE!");
+ }
+
+ /**
+ * Parse encoded string value.
+ *
+ * @param pduDataStream pdu data input stream
+ * @return the EncodedStringValue
+ */
+ protected static EncodedStringValue parseEncodedStringValue(ByteArrayInputStream pduDataStream){
+ /**
+ * From OMA-TS-MMS-ENC-V1_3-20050927-C.pdf
+ * Encoded-string-value = Text-string | Value-length Char-set Text-string
+ */
+ assert(null != pduDataStream);
+ pduDataStream.mark(1);
+ EncodedStringValue returnValue = null;
+ int charset = 0;
+ int temp = pduDataStream.read();
+ assert(-1 != temp);
+ int first = temp & 0xFF;
+ if (first == 0) {
+ return new EncodedStringValue("");
+ }
+
+ pduDataStream.reset();
+ if (first < TEXT_MIN) {
+ parseValueLength(pduDataStream);
+
+ charset = parseShortInteger(pduDataStream); //get the "Charset"
+ }
+
+ byte[] textString = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+
+ try {
+ if (0 != charset) {
+ returnValue = new EncodedStringValue(charset, textString);
+ } else {
+ returnValue = new EncodedStringValue(textString);
+ }
+ } catch(Exception e) {
+ return null;
+ }
+
+ return returnValue;
+ }
+
+ /**
+ * Parse Text-String or Quoted-String.
+ *
+ * @param pduDataStream pdu data input stream
+ * @param stringType TYPE_TEXT_STRING or TYPE_QUOTED_STRING
+ * @return the string without End-of-string in byte array
+ */
+ @UnsupportedAppUsage
+ protected static byte[] parseWapString(ByteArrayInputStream pduDataStream,
+ int stringType) {
+ assert(null != pduDataStream);
+ /**
+ * From wap-230-wsp-20010705-a.pdf
+ * Text-string = [Quote] *TEXT End-of-string
+ * If the first character in the TEXT is in the range of 128-255,
+ * a Quote character must precede it.
+ * Otherwise the Quote character must be omitted.
+ * The Quote is not part of the contents.
+ * Quote = <Octet 127>
+ * End-of-string = <Octet 0>
+ *
+ * Quoted-string = <Octet 34> *TEXT End-of-string
+ *
+ * Token-text = Token End-of-string
+ */
+
+ // Mark supposed beginning of Text-string
+ // We will have to mark again if first char is QUOTE or QUOTED_STRING_FLAG
+ pduDataStream.mark(1);
+
+ // Check first char
+ int temp = pduDataStream.read();
+ assert(-1 != temp);
+ if ((TYPE_QUOTED_STRING == stringType) &&
+ (QUOTED_STRING_FLAG == temp)) {
+ // Mark again if QUOTED_STRING_FLAG and ignore it
+ pduDataStream.mark(1);
+ } else if ((TYPE_TEXT_STRING == stringType) &&
+ (QUOTE == temp)) {
+ // Mark again if QUOTE and ignore it
+ pduDataStream.mark(1);
+ } else {
+ // Otherwise go back to origin
+ pduDataStream.reset();
+ }
+
+ // We are now definitely at the beginning of string
+ /**
+ * Return *TOKEN or *TEXT (Text-String without QUOTE,
+ * Quoted-String without QUOTED_STRING_FLAG and without End-of-string)
+ */
+ return getWapString(pduDataStream, stringType);
+ }
+
+ /**
+ * Check TOKEN data defined in RFC2616.
+ * @param ch checking data
+ * @return true when ch is TOKEN, false when ch is not TOKEN
+ */
+ protected static boolean isTokenCharacter(int ch) {
+ /**
+ * Token = 1*<any CHAR except CTLs or separators>
+ * separators = "("(40) | ")"(41) | "<"(60) | ">"(62) | "@"(64)
+ * | ","(44) | ";"(59) | ":"(58) | "\"(92) | <">(34)
+ * | "/"(47) | "["(91) | "]"(93) | "?"(63) | "="(61)
+ * | "{"(123) | "}"(125) | SP(32) | HT(9)
+ * CHAR = <any US-ASCII character (octets 0 - 127)>
+ * CTL = <any US-ASCII control character
+ * (octets 0 - 31) and DEL (127)>
+ * SP = <US-ASCII SP, space (32)>
+ * HT = <US-ASCII HT, horizontal-tab (9)>
+ */
+ if((ch < 33) || (ch > 126)) {
+ return false;
+ }
+
+ switch(ch) {
+ case '"': /* '"' */
+ case '(': /* '(' */
+ case ')': /* ')' */
+ case ',': /* ',' */
+ case '/': /* '/' */
+ case ':': /* ':' */
+ case ';': /* ';' */
+ case '<': /* '<' */
+ case '=': /* '=' */
+ case '>': /* '>' */
+ case '?': /* '?' */
+ case '@': /* '@' */
+ case '[': /* '[' */
+ case '\\': /* '\' */
+ case ']': /* ']' */
+ case '{': /* '{' */
+ case '}': /* '}' */
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check TEXT data defined in RFC2616.
+ * @param ch checking data
+ * @return true when ch is TEXT, false when ch is not TEXT
+ */
+ protected static boolean isText(int ch) {
+ /**
+ * TEXT = <any OCTET except CTLs,
+ * but including LWS>
+ * CTL = <any US-ASCII control character
+ * (octets 0 - 31) and DEL (127)>
+ * LWS = [CRLF] 1*( SP | HT )
+ * CRLF = CR LF
+ * CR = <US-ASCII CR, carriage return (13)>
+ * LF = <US-ASCII LF, linefeed (10)>
+ */
+ if(((ch >= 32) && (ch <= 126)) || ((ch >= 128) && (ch <= 255))) {
+ return true;
+ }
+
+ switch(ch) {
+ case '\t': /* '\t' */
+ case '\n': /* '\n' */
+ case '\r': /* '\r' */
+ return true;
+ }
+
+ return false;
+ }
+
+ protected static byte[] getWapString(ByteArrayInputStream pduDataStream,
+ int stringType) {
+ assert(null != pduDataStream);
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ int temp = pduDataStream.read();
+ assert(-1 != temp);
+ while((-1 != temp) && ('\0' != temp)) {
+ // check each of the character
+ if (stringType == TYPE_TOKEN_STRING) {
+ if (isTokenCharacter(temp)) {
+ out.write(temp);
+ }
+ } else {
+ if (isText(temp)) {
+ out.write(temp);
+ }
+ }
+
+ temp = pduDataStream.read();
+ assert(-1 != temp);
+ }
+
+ if (out.size() > 0) {
+ return out.toByteArray();
+ }
+
+ return null;
+ }
+
+ /**
+ * Extract a byte value from the input stream.
+ *
+ * @param pduDataStream pdu data input stream
+ * @return the byte
+ */
+ protected static int extractByteValue(ByteArrayInputStream pduDataStream) {
+ assert(null != pduDataStream);
+ int temp = pduDataStream.read();
+ assert(-1 != temp);
+ return temp & 0xFF;
+ }
+
+ /**
+ * Parse Short-Integer.
+ *
+ * @param pduDataStream pdu data input stream
+ * @return the byte
+ */
+ @UnsupportedAppUsage
+ protected static int parseShortInteger(ByteArrayInputStream pduDataStream) {
+ /**
+ * From wap-230-wsp-20010705-a.pdf
+ * Short-integer = OCTET
+ * Integers in range 0-127 shall be encoded as a one
+ * octet value with the most significant bit set to one (1xxx xxxx)
+ * and with the value in the remaining least significant bits.
+ */
+ assert(null != pduDataStream);
+ int temp = pduDataStream.read();
+ assert(-1 != temp);
+ return temp & 0x7F;
+ }
+
+ /**
+ * Parse Long-Integer.
+ *
+ * @param pduDataStream pdu data input stream
+ * @return long integer
+ */
+ protected static long parseLongInteger(ByteArrayInputStream pduDataStream) {
+ /**
+ * From wap-230-wsp-20010705-a.pdf
+ * Long-integer = Short-length Multi-octet-integer
+ * The Short-length indicates the length of the Multi-octet-integer
+ * Multi-octet-integer = 1*30 OCTET
+ * The content octets shall be an unsigned integer value
+ * with the most significant octet encoded first (big-endian representation).
+ * The minimum number of octets must be used to encode the value.
+ * Short-length = <Any octet 0-30>
+ */
+ assert(null != pduDataStream);
+ int temp = pduDataStream.read();
+ assert(-1 != temp);
+ int count = temp & 0xFF;
+
+ if (count > LONG_INTEGER_LENGTH_MAX) {
+ throw new RuntimeException("Octet count greater than 8 and I can't represent that!");
+ }
+
+ long result = 0;
+
+ for (int i = 0 ; i < count ; i++) {
+ temp = pduDataStream.read();
+ assert(-1 != temp);
+ result <<= 8;
+ result += (temp & 0xFF);
+ }
+
+ return result;
+ }
+
+ /**
+ * Parse Integer-Value.
+ *
+ * @param pduDataStream pdu data input stream
+ * @return long integer
+ */
+ protected static long parseIntegerValue(ByteArrayInputStream pduDataStream) {
+ /**
+ * From wap-230-wsp-20010705-a.pdf
+ * Integer-Value = Short-integer | Long-integer
+ */
+ assert(null != pduDataStream);
+ pduDataStream.mark(1);
+ int temp = pduDataStream.read();
+ assert(-1 != temp);
+ pduDataStream.reset();
+ if (temp > SHORT_INTEGER_MAX) {
+ return parseShortInteger(pduDataStream);
+ } else {
+ return parseLongInteger(pduDataStream);
+ }
+ }
+
+ /**
+ * To skip length of the wap value.
+ *
+ * @param pduDataStream pdu data input stream
+ * @param length area size
+ * @return the values in this area
+ */
+ protected static int skipWapValue(ByteArrayInputStream pduDataStream, int length) {
+ assert(null != pduDataStream);
+ byte[] area = new byte[length];
+ int readLen = pduDataStream.read(area, 0, length);
+ if (readLen < length) { //The actually read length is lower than the length
+ return -1;
+ } else {
+ return readLen;
+ }
+ }
+
+ /**
+ * Parse content type parameters. For now we just support
+ * four parameters used in mms: "type", "start", "name", "charset".
+ *
+ * @param pduDataStream pdu data input stream
+ * @param map to store parameters of Content-Type field
+ * @param length length of all the parameters
+ */
+ protected static void parseContentTypeParams(ByteArrayInputStream pduDataStream,
+ HashMap<Integer, Object> map, Integer length) {
+ /**
+ * From wap-230-wsp-20010705-a.pdf
+ * Parameter = Typed-parameter | Untyped-parameter
+ * Typed-parameter = Well-known-parameter-token Typed-value
+ * the actual expected type of the value is implied by the well-known parameter
+ * Well-known-parameter-token = Integer-value
+ * the code values used for parameters are specified in the Assigned Numbers appendix
+ * Typed-value = Compact-value | Text-value
+ * In addition to the expected type, there may be no value.
+ * If the value cannot be encoded using the expected type, it shall be encoded as text.
+ * Compact-value = Integer-value |
+ * Date-value | Delta-seconds-value | Q-value | Version-value |
+ * Uri-value
+ * Untyped-parameter = Token-text Untyped-value
+ * the type of the value is unknown, but it shall be encoded as an integer,
+ * if that is possible.
+ * Untyped-value = Integer-value | Text-value
+ */
+ assert(null != pduDataStream);
+ assert(length > 0);
+
+ int startPos = pduDataStream.available();
+ int tempPos = 0;
+ int lastLen = length;
+ while(0 < lastLen) {
+ int param = pduDataStream.read();
+ assert(-1 != param);
+ lastLen--;
+
+ switch (param) {
+ /**
+ * From rfc2387, chapter 3.1
+ * The type parameter must be specified and its value is the MIME media
+ * type of the "root" body part. It permits a MIME user agent to
+ * determine the content-type without reference to the enclosed body
+ * part. If the value of the type parameter and the root body part's
+ * content-type differ then the User Agent's behavior is undefined.
+ *
+ * From wap-230-wsp-20010705-a.pdf
+ * type = Constrained-encoding
+ * Constrained-encoding = Extension-Media | Short-integer
+ * Extension-media = *TEXT End-of-string
+ */
+ case PduPart.P_TYPE:
+ case PduPart.P_CT_MR_TYPE:
+ pduDataStream.mark(1);
+ int first = extractByteValue(pduDataStream);
+ pduDataStream.reset();
+ if (first > TEXT_MAX) {
+ // Short-integer (well-known type)
+ int index = parseShortInteger(pduDataStream);
+
+ if (index < PduContentTypes.contentTypes.length) {
+ byte[] type = (PduContentTypes.contentTypes[index]).getBytes();
+ map.put(PduPart.P_TYPE, type);
+ } else {
+ //not support this type, ignore it.
+ }
+ } else {
+ // Text-String (extension-media)
+ byte[] type = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ if ((null != type) && (null != map)) {
+ map.put(PduPart.P_TYPE, type);
+ }
+ }
+
+ tempPos = pduDataStream.available();
+ lastLen = length - (startPos - tempPos);
+ break;
+
+ /**
+ * From oma-ts-mms-conf-v1_3.pdf, chapter 10.2.3.
+ * Start Parameter Referring to Presentation
+ *
+ * From rfc2387, chapter 3.2
+ * The start parameter, if given, is the content-ID of the compound
+ * object's "root". If not present the "root" is the first body part in
+ * the Multipart/Related entity. The "root" is the element the
+ * applications processes first.
+ *
+ * From wap-230-wsp-20010705-a.pdf
+ * start = Text-String
+ */
+ case PduPart.P_START:
+ case PduPart.P_DEP_START:
+ byte[] start = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ if ((null != start) && (null != map)) {
+ map.put(PduPart.P_START, start);
+ }
+
+ tempPos = pduDataStream.available();
+ lastLen = length - (startPos - tempPos);
+ break;
+
+ /**
+ * From oma-ts-mms-conf-v1_3.pdf
+ * In creation, the character set SHALL be either us-ascii
+ * (IANA MIBenum 3) or utf-8 (IANA MIBenum 106)[Unicode].
+ * In retrieval, both us-ascii and utf-8 SHALL be supported.
+ *
+ * From wap-230-wsp-20010705-a.pdf
+ * charset = Well-known-charset|Text-String
+ * Well-known-charset = Any-charset | Integer-value
+ * Both are encoded using values from Character Set
+ * Assignments table in Assigned Numbers
+ * Any-charset = <Octet 128>
+ * Equivalent to the special RFC2616 charset value "*"
+ */
+ case PduPart.P_CHARSET:
+ pduDataStream.mark(1);
+ int firstValue = extractByteValue(pduDataStream);
+ pduDataStream.reset();
+ //Check first char
+ if (((firstValue > TEXT_MIN) && (firstValue < TEXT_MAX)) ||
+ (END_STRING_FLAG == firstValue)) {
+ //Text-String (extension-charset)
+ byte[] charsetStr = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ try {
+ int charsetInt = CharacterSets.getMibEnumValue(
+ new String(charsetStr));
+ map.put(PduPart.P_CHARSET, charsetInt);
+ } catch (UnsupportedEncodingException e) {
+ // Not a well-known charset, use "*".
+ Log.e(LOG_TAG, Arrays.toString(charsetStr), e);
+ map.put(PduPart.P_CHARSET, CharacterSets.ANY_CHARSET);
+ }
+ } else {
+ //Well-known-charset
+ int charset = (int) parseIntegerValue(pduDataStream);
+ if (map != null) {
+ map.put(PduPart.P_CHARSET, charset);
+ }
+ }
+
+ tempPos = pduDataStream.available();
+ lastLen = length - (startPos - tempPos);
+ break;
+
+ /**
+ * From oma-ts-mms-conf-v1_3.pdf
+ * A name for multipart object SHALL be encoded using name-parameter
+ * for Content-Type header in WSP multipart headers.
+ *
+ * From wap-230-wsp-20010705-a.pdf
+ * name = Text-String
+ */
+ case PduPart.P_DEP_NAME:
+ case PduPart.P_NAME:
+ byte[] name = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ if ((null != name) && (null != map)) {
+ map.put(PduPart.P_NAME, name);
+ }
+
+ tempPos = pduDataStream.available();
+ lastLen = length - (startPos - tempPos);
+ break;
+ default:
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "Not supported Content-Type parameter");
+ }
+ if (-1 == skipWapValue(pduDataStream, lastLen)) {
+ Log.e(LOG_TAG, "Corrupt Content-Type");
+ } else {
+ lastLen = 0;
+ }
+ break;
+ }
+ }
+
+ if (0 != lastLen) {
+ Log.e(LOG_TAG, "Corrupt Content-Type");
+ }
+ }
+
+ /**
+ * Parse content type.
+ *
+ * @param pduDataStream pdu data input stream
+ * @param map to store parameters in Content-Type header field
+ * @return Content-Type value
+ */
+ @UnsupportedAppUsage
+ protected static byte[] parseContentType(ByteArrayInputStream pduDataStream,
+ HashMap<Integer, Object> map) {
+ /**
+ * From wap-230-wsp-20010705-a.pdf
+ * Content-type-value = Constrained-media | Content-general-form
+ * Content-general-form = Value-length Media-type
+ * Media-type = (Well-known-media | Extension-Media) *(Parameter)
+ */
+ assert(null != pduDataStream);
+
+ byte[] contentType = null;
+ pduDataStream.mark(1);
+ int temp = pduDataStream.read();
+ assert(-1 != temp);
+ pduDataStream.reset();
+
+ int cur = (temp & 0xFF);
+
+ if (cur < TEXT_MIN) {
+ int length = parseValueLength(pduDataStream);
+ int startPos = pduDataStream.available();
+ pduDataStream.mark(1);
+ temp = pduDataStream.read();
+ assert(-1 != temp);
+ pduDataStream.reset();
+ int first = (temp & 0xFF);
+
+ if ((first >= TEXT_MIN) && (first <= TEXT_MAX)) {
+ contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ } else if (first > TEXT_MAX) {
+ int index = parseShortInteger(pduDataStream);
+
+ if (index < PduContentTypes.contentTypes.length) { //well-known type
+ contentType = (PduContentTypes.contentTypes[index]).getBytes();
+ } else {
+ pduDataStream.reset();
+ contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ }
+ } else {
+ Log.e(LOG_TAG, "Corrupt content-type");
+ return (PduContentTypes.contentTypes[0]).getBytes(); //"*/*"
+ }
+
+ int endPos = pduDataStream.available();
+ int parameterLen = length - (startPos - endPos);
+ if (parameterLen > 0) {//have parameters
+ parseContentTypeParams(pduDataStream, map, parameterLen);
+ }
+
+ if (parameterLen < 0) {
+ Log.e(LOG_TAG, "Corrupt MMS message");
+ return (PduContentTypes.contentTypes[0]).getBytes(); //"*/*"
+ }
+ } else if (cur <= TEXT_MAX) {
+ contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ } else {
+ contentType =
+ (PduContentTypes.contentTypes[parseShortInteger(pduDataStream)]).getBytes();
+ }
+
+ return contentType;
+ }
+
+ /**
+ * Parse part's headers.
+ *
+ * @param pduDataStream pdu data input stream
+ * @param part to store the header informations of the part
+ * @param length length of the headers
+ * @return true if parse successfully, false otherwise
+ */
+ @UnsupportedAppUsage
+ protected boolean parsePartHeaders(ByteArrayInputStream pduDataStream,
+ PduPart part, int length) {
+ assert(null != pduDataStream);
+ assert(null != part);
+ assert(length > 0);
+
+ /**
+ * From oma-ts-mms-conf-v1_3.pdf, chapter 10.2.
+ * A name for multipart object SHALL be encoded using name-parameter
+ * for Content-Type header in WSP multipart headers.
+ * In decoding, name-parameter of Content-Type SHALL be used if available.
+ * If name-parameter of Content-Type is not available,
+ * filename parameter of Content-Disposition header SHALL be used if available.
+ * If neither name-parameter of Content-Type header nor filename parameter
+ * of Content-Disposition header is available,
+ * Content-Location header SHALL be used if available.
+ *
+ * Within SMIL part the reference to the media object parts SHALL use
+ * either Content-ID or Content-Location mechanism [RFC2557]
+ * and the corresponding WSP part headers in media object parts
+ * contain the corresponding definitions.
+ */
+ int startPos = pduDataStream.available();
+ int tempPos = 0;
+ int lastLen = length;
+ while(0 < lastLen) {
+ int header = pduDataStream.read();
+ assert(-1 != header);
+ lastLen--;
+
+ if (header > TEXT_MAX) {
+ // Number assigned headers.
+ switch (header) {
+ case PduPart.P_CONTENT_LOCATION:
+ /**
+ * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
+ * Content-location-value = Uri-value
+ */
+ byte[] contentLocation = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ if (null != contentLocation) {
+ part.setContentLocation(contentLocation);
+ }
+
+ tempPos = pduDataStream.available();
+ lastLen = length - (startPos - tempPos);
+ break;
+ case PduPart.P_CONTENT_ID:
+ /**
+ * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
+ * Content-ID-value = Quoted-string
+ */
+ byte[] contentId = parseWapString(pduDataStream, TYPE_QUOTED_STRING);
+ if (null != contentId) {
+ part.setContentId(contentId);
+ }
+
+ tempPos = pduDataStream.available();
+ lastLen = length - (startPos - tempPos);
+ break;
+ case PduPart.P_DEP_CONTENT_DISPOSITION:
+ case PduPart.P_CONTENT_DISPOSITION:
+ /**
+ * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
+ * Content-disposition-value = Value-length Disposition *(Parameter)
+ * Disposition = Form-data | Attachment | Inline | Token-text
+ * Form-data = <Octet 128>
+ * Attachment = <Octet 129>
+ * Inline = <Octet 130>
+ */
+
+ /*
+ * some carrier mmsc servers do not support content_disposition
+ * field correctly
+ */
+ if (mParseContentDisposition) {
+ int len = parseValueLength(pduDataStream);
+ pduDataStream.mark(1);
+ int thisStartPos = pduDataStream.available();
+ int thisEndPos = 0;
+ int value = pduDataStream.read();
+
+ if (value == PduPart.P_DISPOSITION_FROM_DATA ) {
+ part.setContentDisposition(PduPart.DISPOSITION_FROM_DATA);
+ } else if (value == PduPart.P_DISPOSITION_ATTACHMENT) {
+ part.setContentDisposition(PduPart.DISPOSITION_ATTACHMENT);
+ } else if (value == PduPart.P_DISPOSITION_INLINE) {
+ part.setContentDisposition(PduPart.DISPOSITION_INLINE);
+ } else {
+ pduDataStream.reset();
+ /* Token-text */
+ part.setContentDisposition(parseWapString(pduDataStream
+ , TYPE_TEXT_STRING));
+ }
+
+ /* get filename parameter and skip other parameters */
+ thisEndPos = pduDataStream.available();
+ if (thisStartPos - thisEndPos < len) {
+ value = pduDataStream.read();
+ if (value == PduPart.P_FILENAME) { //filename is text-string
+ part.setFilename(parseWapString(pduDataStream
+ , TYPE_TEXT_STRING));
+ }
+
+ /* skip other parameters */
+ thisEndPos = pduDataStream.available();
+ if (thisStartPos - thisEndPos < len) {
+ int last = len - (thisStartPos - thisEndPos);
+ byte[] temp = new byte[last];
+ pduDataStream.read(temp, 0, last);
+ }
+ }
+
+ tempPos = pduDataStream.available();
+ lastLen = length - (startPos - tempPos);
+ }
+ break;
+ default:
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "Not supported Part headers: " + header);
+ }
+ if (-1 == skipWapValue(pduDataStream, lastLen)) {
+ Log.e(LOG_TAG, "Corrupt Part headers");
+ return false;
+ }
+ lastLen = 0;
+ break;
+ }
+ } else if ((header >= TEXT_MIN) && (header <= TEXT_MAX)) {
+ // Not assigned header.
+ byte[] tempHeader = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ byte[] tempValue = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+
+ // Check the header whether it is "Content-Transfer-Encoding".
+ if (true ==
+ PduPart.CONTENT_TRANSFER_ENCODING.equalsIgnoreCase(new String(tempHeader))) {
+ part.setContentTransferEncoding(tempValue);
+ }
+
+ tempPos = pduDataStream.available();
+ lastLen = length - (startPos - tempPos);
+ } else {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "Not supported Part headers: " + header);
+ }
+ // Skip all headers of this part.
+ if (-1 == skipWapValue(pduDataStream, lastLen)) {
+ Log.e(LOG_TAG, "Corrupt Part headers");
+ return false;
+ }
+ lastLen = 0;
+ }
+ }
+
+ if (0 != lastLen) {
+ Log.e(LOG_TAG, "Corrupt Part headers");
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check the position of a specified part.
+ *
+ * @param part the part to be checked
+ * @return part position, THE_FIRST_PART when it's the
+ * first one, THE_LAST_PART when it's the last one.
+ */
+ @UnsupportedAppUsage
+ private static int checkPartPosition(PduPart part) {
+ assert(null != part);
+ if ((null == mTypeParam) &&
+ (null == mStartParam)) {
+ return THE_LAST_PART;
+ }
+
+ /* check part's content-id */
+ if (null != mStartParam) {
+ byte[] contentId = part.getContentId();
+ if (null != contentId) {
+ if (true == Arrays.equals(mStartParam, contentId)) {
+ return THE_FIRST_PART;
+ }
+ }
+ // This is not the first part, so append to end (keeping the original order)
+ // Check b/19607294 for details of this change
+ return THE_LAST_PART;
+ }
+
+ /* check part's content-type */
+ if (null != mTypeParam) {
+ byte[] contentType = part.getContentType();
+ if (null != contentType) {
+ if (true == Arrays.equals(mTypeParam, contentType)) {
+ return THE_FIRST_PART;
+ }
+ }
+ }
+
+ return THE_LAST_PART;
+ }
+
+ /**
+ * Check mandatory headers of a pdu.
+ *
+ * @param headers pdu headers
+ * @return true if the pdu has all of the mandatory headers, false otherwise.
+ */
+ protected static boolean checkMandatoryHeader(PduHeaders headers) {
+ if (null == headers) {
+ return false;
+ }
+
+ /* get message type */
+ int messageType = headers.getOctet(PduHeaders.MESSAGE_TYPE);
+
+ /* check Mms-Version field */
+ int mmsVersion = headers.getOctet(PduHeaders.MMS_VERSION);
+ if (0 == mmsVersion) {
+ // Every message should have Mms-Version field.
+ return false;
+ }
+
+ /* check mandatory header fields */
+ switch (messageType) {
+ case PduHeaders.MESSAGE_TYPE_SEND_REQ:
+ // Content-Type field.
+ byte[] srContentType = headers.getTextString(PduHeaders.CONTENT_TYPE);
+ if (null == srContentType) {
+ return false;
+ }
+
+ // From field.
+ EncodedStringValue srFrom = headers.getEncodedStringValue(PduHeaders.FROM);
+ if (null == srFrom) {
+ return false;
+ }
+
+ // Transaction-Id field.
+ byte[] srTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
+ if (null == srTransactionId) {
+ return false;
+ }
+
+ break;
+ case PduHeaders.MESSAGE_TYPE_SEND_CONF:
+ // Response-Status field.
+ int scResponseStatus = headers.getOctet(PduHeaders.RESPONSE_STATUS);
+ if (0 == scResponseStatus) {
+ return false;
+ }
+
+ // Transaction-Id field.
+ byte[] scTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
+ if (null == scTransactionId) {
+ return false;
+ }
+
+ break;
+ case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
+ // Content-Location field.
+ byte[] niContentLocation = headers.getTextString(PduHeaders.CONTENT_LOCATION);
+ if (null == niContentLocation) {
+ return false;
+ }
+
+ // Expiry field.
+ long niExpiry = headers.getLongInteger(PduHeaders.EXPIRY);
+ if (-1 == niExpiry) {
+ return false;
+ }
+
+ // Message-Class field.
+ byte[] niMessageClass = headers.getTextString(PduHeaders.MESSAGE_CLASS);
+ if (null == niMessageClass) {
+ return false;
+ }
+
+ // Message-Size field.
+ long niMessageSize = headers.getLongInteger(PduHeaders.MESSAGE_SIZE);
+ if (-1 == niMessageSize) {
+ return false;
+ }
+
+ // Transaction-Id field.
+ byte[] niTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
+ if (null == niTransactionId) {
+ return false;
+ }
+
+ break;
+ case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
+ // Status field.
+ int nriStatus = headers.getOctet(PduHeaders.STATUS);
+ if (0 == nriStatus) {
+ return false;
+ }
+
+ // Transaction-Id field.
+ byte[] nriTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
+ if (null == nriTransactionId) {
+ return false;
+ }
+
+ break;
+ case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
+ // Content-Type field.
+ byte[] rcContentType = headers.getTextString(PduHeaders.CONTENT_TYPE);
+ if (null == rcContentType) {
+ return false;
+ }
+
+ // Date field.
+ long rcDate = headers.getLongInteger(PduHeaders.DATE);
+ if (-1 == rcDate) {
+ return false;
+ }
+
+ break;
+ case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
+ // Date field.
+ long diDate = headers.getLongInteger(PduHeaders.DATE);
+ if (-1 == diDate) {
+ return false;
+ }
+
+ // Message-Id field.
+ byte[] diMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
+ if (null == diMessageId) {
+ return false;
+ }
+
+ // Status field.
+ int diStatus = headers.getOctet(PduHeaders.STATUS);
+ if (0 == diStatus) {
+ return false;
+ }
+
+ // To field.
+ EncodedStringValue[] diTo = headers.getEncodedStringValues(PduHeaders.TO);
+ if (null == diTo) {
+ return false;
+ }
+
+ break;
+ case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
+ // Transaction-Id field.
+ byte[] aiTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
+ if (null == aiTransactionId) {
+ return false;
+ }
+
+ break;
+ case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
+ // Date field.
+ long roDate = headers.getLongInteger(PduHeaders.DATE);
+ if (-1 == roDate) {
+ return false;
+ }
+
+ // From field.
+ EncodedStringValue roFrom = headers.getEncodedStringValue(PduHeaders.FROM);
+ if (null == roFrom) {
+ return false;
+ }
+
+ // Message-Id field.
+ byte[] roMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
+ if (null == roMessageId) {
+ return false;
+ }
+
+ // Read-Status field.
+ int roReadStatus = headers.getOctet(PduHeaders.READ_STATUS);
+ if (0 == roReadStatus) {
+ return false;
+ }
+
+ // To field.
+ EncodedStringValue[] roTo = headers.getEncodedStringValues(PduHeaders.TO);
+ if (null == roTo) {
+ return false;
+ }
+
+ break;
+ case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
+ // From field.
+ EncodedStringValue rrFrom = headers.getEncodedStringValue(PduHeaders.FROM);
+ if (null == rrFrom) {
+ return false;
+ }
+
+ // Message-Id field.
+ byte[] rrMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
+ if (null == rrMessageId) {
+ return false;
+ }
+
+ // Read-Status field.
+ int rrReadStatus = headers.getOctet(PduHeaders.READ_STATUS);
+ if (0 == rrReadStatus) {
+ return false;
+ }
+
+ // To field.
+ EncodedStringValue[] rrTo = headers.getEncodedStringValues(PduHeaders.TO);
+ if (null == rrTo) {
+ return false;
+ }
+
+ break;
+ default:
+ // Parser doesn't support this message type in this version.
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/PduPart.java b/telephony/java/com/google/android/mms/pdu/PduPart.java
new file mode 100644
index 0000000..09b7751
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/PduPart.java
@@ -0,0 +1,439 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-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.google.android.mms.pdu;
+
+import android.net.Uri;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * The pdu part.
+ */
+public class PduPart {
+ /**
+ * Well-Known Parameters.
+ */
+ public static final int P_Q = 0x80;
+ public static final int P_CHARSET = 0x81;
+ public static final int P_LEVEL = 0x82;
+ public static final int P_TYPE = 0x83;
+ public static final int P_DEP_NAME = 0x85;
+ public static final int P_DEP_FILENAME = 0x86;
+ public static final int P_DIFFERENCES = 0x87;
+ public static final int P_PADDING = 0x88;
+ // This value of "TYPE" s used with Content-Type: multipart/related
+ public static final int P_CT_MR_TYPE = 0x89;
+ public static final int P_DEP_START = 0x8A;
+ public static final int P_DEP_START_INFO = 0x8B;
+ public static final int P_DEP_COMMENT = 0x8C;
+ public static final int P_DEP_DOMAIN = 0x8D;
+ public static final int P_MAX_AGE = 0x8E;
+ public static final int P_DEP_PATH = 0x8F;
+ public static final int P_SECURE = 0x90;
+ public static final int P_SEC = 0x91;
+ public static final int P_MAC = 0x92;
+ public static final int P_CREATION_DATE = 0x93;
+ public static final int P_MODIFICATION_DATE = 0x94;
+ public static final int P_READ_DATE = 0x95;
+ public static final int P_SIZE = 0x96;
+ public static final int P_NAME = 0x97;
+ public static final int P_FILENAME = 0x98;
+ public static final int P_START = 0x99;
+ public static final int P_START_INFO = 0x9A;
+ public static final int P_COMMENT = 0x9B;
+ public static final int P_DOMAIN = 0x9C;
+ public static final int P_PATH = 0x9D;
+
+ /**
+ * Header field names.
+ */
+ public static final int P_CONTENT_TYPE = 0x91;
+ public static final int P_CONTENT_LOCATION = 0x8E;
+ public static final int P_CONTENT_ID = 0xC0;
+ public static final int P_DEP_CONTENT_DISPOSITION = 0xAE;
+ public static final int P_CONTENT_DISPOSITION = 0xC5;
+ // The next header is unassigned header, use reserved header(0x48) value.
+ public static final int P_CONTENT_TRANSFER_ENCODING = 0xC8;
+
+ /**
+ * Content=Transfer-Encoding string.
+ */
+ public static final String CONTENT_TRANSFER_ENCODING =
+ "Content-Transfer-Encoding";
+
+ /**
+ * Value of Content-Transfer-Encoding.
+ */
+ public static final String P_BINARY = "binary";
+ public static final String P_7BIT = "7bit";
+ public static final String P_8BIT = "8bit";
+ public static final String P_BASE64 = "base64";
+ public static final String P_QUOTED_PRINTABLE = "quoted-printable";
+
+ /**
+ * Value of disposition can be set to PduPart when the value is octet in
+ * the PDU.
+ * "from-data" instead of Form-data<Octet 128>.
+ * "attachment" instead of Attachment<Octet 129>.
+ * "inline" instead of Inline<Octet 130>.
+ */
+ static final byte[] DISPOSITION_FROM_DATA = "from-data".getBytes();
+ static final byte[] DISPOSITION_ATTACHMENT = "attachment".getBytes();
+ static final byte[] DISPOSITION_INLINE = "inline".getBytes();
+
+ /**
+ * Content-Disposition value.
+ */
+ public static final int P_DISPOSITION_FROM_DATA = 0x80;
+ public static final int P_DISPOSITION_ATTACHMENT = 0x81;
+ public static final int P_DISPOSITION_INLINE = 0x82;
+
+ /**
+ * Header of part.
+ */
+ private Map<Integer, Object> mPartHeader = null;
+
+ /**
+ * Data uri.
+ */
+ private Uri mUri = null;
+
+ /**
+ * Part data.
+ */
+ private byte[] mPartData = null;
+
+ private static final String TAG = "PduPart";
+
+ /**
+ * Empty Constructor.
+ */
+ @UnsupportedAppUsage
+ public PduPart() {
+ mPartHeader = new HashMap<Integer, Object>();
+ }
+
+ /**
+ * Set part data. The data are stored as byte array.
+ *
+ * @param data the data
+ */
+ @UnsupportedAppUsage
+ public void setData(byte[] data) {
+ if(data == null) {
+ return;
+ }
+
+ mPartData = new byte[data.length];
+ System.arraycopy(data, 0, mPartData, 0, data.length);
+ }
+
+ /**
+ * @return A copy of the part data or null if the data wasn't set or
+ * the data is stored as Uri.
+ * @see #getDataUri
+ */
+ @UnsupportedAppUsage
+ public byte[] getData() {
+ if(mPartData == null) {
+ return null;
+ }
+
+ byte[] byteArray = new byte[mPartData.length];
+ System.arraycopy(mPartData, 0, byteArray, 0, mPartData.length);
+ return byteArray;
+ }
+
+ /**
+ * @return The length of the data, if this object have data, else 0.
+ */
+ @UnsupportedAppUsage
+ public int getDataLength() {
+ if(mPartData != null){
+ return mPartData.length;
+ } else {
+ return 0;
+ }
+ }
+
+
+ /**
+ * Set data uri. The data are stored as Uri.
+ *
+ * @param uri the uri
+ */
+ @UnsupportedAppUsage
+ public void setDataUri(Uri uri) {
+ mUri = uri;
+ }
+
+ /**
+ * @return The Uri of the part data or null if the data wasn't set or
+ * the data is stored as byte array.
+ * @see #getData
+ */
+ @UnsupportedAppUsage
+ public Uri getDataUri() {
+ return mUri;
+ }
+
+ /**
+ * Set Content-id value
+ *
+ * @param contentId the content-id value
+ * @throws NullPointerException if the value is null.
+ */
+ @UnsupportedAppUsage
+ public void setContentId(byte[] contentId) {
+ if((contentId == null) || (contentId.length == 0)) {
+ throw new IllegalArgumentException(
+ "Content-Id may not be null or empty.");
+ }
+
+ if ((contentId.length > 1)
+ && ((char) contentId[0] == '<')
+ && ((char) contentId[contentId.length - 1] == '>')) {
+ mPartHeader.put(P_CONTENT_ID, contentId);
+ return;
+ }
+
+ // Insert beginning '<' and trailing '>' for Content-Id.
+ byte[] buffer = new byte[contentId.length + 2];
+ buffer[0] = (byte) (0xff & '<');
+ buffer[buffer.length - 1] = (byte) (0xff & '>');
+ System.arraycopy(contentId, 0, buffer, 1, contentId.length);
+ mPartHeader.put(P_CONTENT_ID, buffer);
+ }
+
+ /**
+ * Get Content-id value.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public byte[] getContentId() {
+ return (byte[]) mPartHeader.get(P_CONTENT_ID);
+ }
+
+ /**
+ * Set Char-set value.
+ *
+ * @param charset the value
+ */
+ @UnsupportedAppUsage
+ public void setCharset(int charset) {
+ mPartHeader.put(P_CHARSET, charset);
+ }
+
+ /**
+ * Get Char-set value
+ *
+ * @return the charset value. Return 0 if charset was not set.
+ */
+ @UnsupportedAppUsage
+ public int getCharset() {
+ Integer charset = (Integer) mPartHeader.get(P_CHARSET);
+ if(charset == null) {
+ return 0;
+ } else {
+ return charset.intValue();
+ }
+ }
+
+ /**
+ * Set Content-Location value.
+ *
+ * @param contentLocation the value
+ * @throws NullPointerException if the value is null.
+ */
+ @UnsupportedAppUsage
+ public void setContentLocation(byte[] contentLocation) {
+ if(contentLocation == null) {
+ throw new NullPointerException("null content-location");
+ }
+
+ mPartHeader.put(P_CONTENT_LOCATION, contentLocation);
+ }
+
+ /**
+ * Get Content-Location value.
+ *
+ * @return the value
+ * return PduPart.disposition[0] instead of <Octet 128> (Form-data).
+ * return PduPart.disposition[1] instead of <Octet 129> (Attachment).
+ * return PduPart.disposition[2] instead of <Octet 130> (Inline).
+ */
+ @UnsupportedAppUsage
+ public byte[] getContentLocation() {
+ return (byte[]) mPartHeader.get(P_CONTENT_LOCATION);
+ }
+
+ /**
+ * Set Content-Disposition value.
+ * Use PduPart.disposition[0] instead of <Octet 128> (Form-data).
+ * Use PduPart.disposition[1] instead of <Octet 129> (Attachment).
+ * Use PduPart.disposition[2] instead of <Octet 130> (Inline).
+ *
+ * @param contentDisposition the value
+ * @throws NullPointerException if the value is null.
+ */
+ @UnsupportedAppUsage
+ public void setContentDisposition(byte[] contentDisposition) {
+ if(contentDisposition == null) {
+ throw new NullPointerException("null content-disposition");
+ }
+
+ mPartHeader.put(P_CONTENT_DISPOSITION, contentDisposition);
+ }
+
+ /**
+ * Get Content-Disposition value.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public byte[] getContentDisposition() {
+ return (byte[]) mPartHeader.get(P_CONTENT_DISPOSITION);
+ }
+
+ /**
+ * Set Content-Type value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ @UnsupportedAppUsage
+ public void setContentType(byte[] contentType) {
+ if(contentType == null) {
+ throw new NullPointerException("null content-type");
+ }
+
+ mPartHeader.put(P_CONTENT_TYPE, contentType);
+ }
+
+ /**
+ * Get Content-Type value of part.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public byte[] getContentType() {
+ return (byte[]) mPartHeader.get(P_CONTENT_TYPE);
+ }
+
+ /**
+ * Set Content-Transfer-Encoding value
+ *
+ * @param contentId the content-id value
+ * @throws NullPointerException if the value is null.
+ */
+ @UnsupportedAppUsage
+ public void setContentTransferEncoding(byte[] contentTransferEncoding) {
+ if(contentTransferEncoding == null) {
+ throw new NullPointerException("null content-transfer-encoding");
+ }
+
+ mPartHeader.put(P_CONTENT_TRANSFER_ENCODING, contentTransferEncoding);
+ }
+
+ /**
+ * Get Content-Transfer-Encoding value.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public byte[] getContentTransferEncoding() {
+ return (byte[]) mPartHeader.get(P_CONTENT_TRANSFER_ENCODING);
+ }
+
+ /**
+ * Set Content-type parameter: name.
+ *
+ * @param name the name value
+ * @throws NullPointerException if the value is null.
+ */
+ @UnsupportedAppUsage
+ public void setName(byte[] name) {
+ if(null == name) {
+ throw new NullPointerException("null content-id");
+ }
+
+ mPartHeader.put(P_NAME, name);
+ }
+
+ /**
+ * Get content-type parameter: name.
+ *
+ * @return the name
+ */
+ @UnsupportedAppUsage
+ public byte[] getName() {
+ return (byte[]) mPartHeader.get(P_NAME);
+ }
+
+ /**
+ * Get Content-disposition parameter: filename
+ *
+ * @param fileName the filename value
+ * @throws NullPointerException if the value is null.
+ */
+ @UnsupportedAppUsage
+ public void setFilename(byte[] fileName) {
+ if(null == fileName) {
+ throw new NullPointerException("null content-id");
+ }
+
+ mPartHeader.put(P_FILENAME, fileName);
+ }
+
+ /**
+ * Set Content-disposition parameter: filename
+ *
+ * @return the filename
+ */
+ @UnsupportedAppUsage
+ public byte[] getFilename() {
+ return (byte[]) mPartHeader.get(P_FILENAME);
+ }
+
+ @UnsupportedAppUsage
+ public String generateLocation() {
+ // Assumption: At least one of the content-location / name / filename
+ // or content-id should be set. This is guaranteed by the PduParser
+ // for incoming messages and by MM composer for outgoing messages.
+ byte[] location = (byte[]) mPartHeader.get(P_NAME);
+ if(null == location) {
+ location = (byte[]) mPartHeader.get(P_FILENAME);
+
+ if (null == location) {
+ location = (byte[]) mPartHeader.get(P_CONTENT_LOCATION);
+ }
+ }
+
+ if (null == location) {
+ byte[] contentId = (byte[]) mPartHeader.get(P_CONTENT_ID);
+ return "cid:" + new String(contentId);
+ } else {
+ return new String(location);
+ }
+ }
+}
+
diff --git a/telephony/java/com/google/android/mms/pdu/PduPersister.java b/telephony/java/com/google/android/mms/pdu/PduPersister.java
new file mode 100755
index 0000000..93f3065
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/PduPersister.java
@@ -0,0 +1,1573 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-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.google.android.mms.pdu;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.drm.DrmManagerClient;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.provider.Telephony;
+import android.provider.Telephony.Mms;
+import android.provider.Telephony.Mms.Addr;
+import android.provider.Telephony.Mms.Part;
+import android.provider.Telephony.MmsSms;
+import android.provider.Telephony.MmsSms.PendingMessages;
+import android.provider.Telephony.Threads;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.ContentType;
+import com.google.android.mms.InvalidHeaderValueException;
+import com.google.android.mms.MmsException;
+import com.google.android.mms.util.DownloadDrmHelper;
+import com.google.android.mms.util.DrmConvertSession;
+import com.google.android.mms.util.PduCache;
+import com.google.android.mms.util.PduCacheEntry;
+import com.google.android.mms.util.SqliteWrapper;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * This class is the high-level manager of PDU storage.
+ */
+public class PduPersister {
+ private static final String TAG = "PduPersister";
+ private static final boolean DEBUG = false;
+ private static final boolean LOCAL_LOGV = false;
+
+ private static final long DUMMY_THREAD_ID = Long.MAX_VALUE;
+
+ /**
+ * The uri of temporary drm objects.
+ */
+ public static final String TEMPORARY_DRM_OBJECT_URI =
+ "content://mms/" + Long.MAX_VALUE + "/part";
+ /**
+ * Indicate that we transiently failed to process a MM.
+ */
+ public static final int PROC_STATUS_TRANSIENT_FAILURE = 1;
+ /**
+ * Indicate that we permanently failed to process a MM.
+ */
+ public static final int PROC_STATUS_PERMANENTLY_FAILURE = 2;
+ /**
+ * Indicate that we have successfully processed a MM.
+ */
+ public static final int PROC_STATUS_COMPLETED = 3;
+
+ private static PduPersister sPersister;
+ @UnsupportedAppUsage
+ private static final PduCache PDU_CACHE_INSTANCE;
+
+ @UnsupportedAppUsage
+ private static final int[] ADDRESS_FIELDS = new int[] {
+ PduHeaders.BCC,
+ PduHeaders.CC,
+ PduHeaders.FROM,
+ PduHeaders.TO
+ };
+
+ private static final String[] PDU_PROJECTION = new String[] {
+ Mms._ID,
+ Mms.MESSAGE_BOX,
+ Mms.THREAD_ID,
+ Mms.RETRIEVE_TEXT,
+ Mms.SUBJECT,
+ Mms.CONTENT_LOCATION,
+ Mms.CONTENT_TYPE,
+ Mms.MESSAGE_CLASS,
+ Mms.MESSAGE_ID,
+ Mms.RESPONSE_TEXT,
+ Mms.TRANSACTION_ID,
+ Mms.CONTENT_CLASS,
+ Mms.DELIVERY_REPORT,
+ Mms.MESSAGE_TYPE,
+ Mms.MMS_VERSION,
+ Mms.PRIORITY,
+ Mms.READ_REPORT,
+ Mms.READ_STATUS,
+ Mms.REPORT_ALLOWED,
+ Mms.RETRIEVE_STATUS,
+ Mms.STATUS,
+ Mms.DATE,
+ Mms.DELIVERY_TIME,
+ Mms.EXPIRY,
+ Mms.MESSAGE_SIZE,
+ Mms.SUBJECT_CHARSET,
+ Mms.RETRIEVE_TEXT_CHARSET,
+ };
+
+ private static final int PDU_COLUMN_ID = 0;
+ private static final int PDU_COLUMN_MESSAGE_BOX = 1;
+ private static final int PDU_COLUMN_THREAD_ID = 2;
+ private static final int PDU_COLUMN_RETRIEVE_TEXT = 3;
+ private static final int PDU_COLUMN_SUBJECT = 4;
+ private static final int PDU_COLUMN_CONTENT_LOCATION = 5;
+ private static final int PDU_COLUMN_CONTENT_TYPE = 6;
+ private static final int PDU_COLUMN_MESSAGE_CLASS = 7;
+ private static final int PDU_COLUMN_MESSAGE_ID = 8;
+ private static final int PDU_COLUMN_RESPONSE_TEXT = 9;
+ private static final int PDU_COLUMN_TRANSACTION_ID = 10;
+ private static final int PDU_COLUMN_CONTENT_CLASS = 11;
+ private static final int PDU_COLUMN_DELIVERY_REPORT = 12;
+ private static final int PDU_COLUMN_MESSAGE_TYPE = 13;
+ private static final int PDU_COLUMN_MMS_VERSION = 14;
+ private static final int PDU_COLUMN_PRIORITY = 15;
+ private static final int PDU_COLUMN_READ_REPORT = 16;
+ private static final int PDU_COLUMN_READ_STATUS = 17;
+ private static final int PDU_COLUMN_REPORT_ALLOWED = 18;
+ private static final int PDU_COLUMN_RETRIEVE_STATUS = 19;
+ private static final int PDU_COLUMN_STATUS = 20;
+ private static final int PDU_COLUMN_DATE = 21;
+ private static final int PDU_COLUMN_DELIVERY_TIME = 22;
+ private static final int PDU_COLUMN_EXPIRY = 23;
+ private static final int PDU_COLUMN_MESSAGE_SIZE = 24;
+ private static final int PDU_COLUMN_SUBJECT_CHARSET = 25;
+ private static final int PDU_COLUMN_RETRIEVE_TEXT_CHARSET = 26;
+
+ @UnsupportedAppUsage
+ private static final String[] PART_PROJECTION = new String[] {
+ Part._ID,
+ Part.CHARSET,
+ Part.CONTENT_DISPOSITION,
+ Part.CONTENT_ID,
+ Part.CONTENT_LOCATION,
+ Part.CONTENT_TYPE,
+ Part.FILENAME,
+ Part.NAME,
+ Part.TEXT
+ };
+
+ private static final int PART_COLUMN_ID = 0;
+ private static final int PART_COLUMN_CHARSET = 1;
+ private static final int PART_COLUMN_CONTENT_DISPOSITION = 2;
+ private static final int PART_COLUMN_CONTENT_ID = 3;
+ private static final int PART_COLUMN_CONTENT_LOCATION = 4;
+ private static final int PART_COLUMN_CONTENT_TYPE = 5;
+ private static final int PART_COLUMN_FILENAME = 6;
+ private static final int PART_COLUMN_NAME = 7;
+ private static final int PART_COLUMN_TEXT = 8;
+
+ @UnsupportedAppUsage
+ private static final HashMap<Uri, Integer> MESSAGE_BOX_MAP;
+ // These map are used for convenience in persist() and load().
+ private static final HashMap<Integer, Integer> CHARSET_COLUMN_INDEX_MAP;
+ private static final HashMap<Integer, Integer> ENCODED_STRING_COLUMN_INDEX_MAP;
+ private static final HashMap<Integer, Integer> TEXT_STRING_COLUMN_INDEX_MAP;
+ private static final HashMap<Integer, Integer> OCTET_COLUMN_INDEX_MAP;
+ private static final HashMap<Integer, Integer> LONG_COLUMN_INDEX_MAP;
+ @UnsupportedAppUsage
+ private static final HashMap<Integer, String> CHARSET_COLUMN_NAME_MAP;
+ @UnsupportedAppUsage
+ private static final HashMap<Integer, String> ENCODED_STRING_COLUMN_NAME_MAP;
+ @UnsupportedAppUsage
+ private static final HashMap<Integer, String> TEXT_STRING_COLUMN_NAME_MAP;
+ @UnsupportedAppUsage
+ private static final HashMap<Integer, String> OCTET_COLUMN_NAME_MAP;
+ @UnsupportedAppUsage
+ private static final HashMap<Integer, String> LONG_COLUMN_NAME_MAP;
+
+ static {
+ MESSAGE_BOX_MAP = new HashMap<Uri, Integer>();
+ MESSAGE_BOX_MAP.put(Mms.Inbox.CONTENT_URI, Mms.MESSAGE_BOX_INBOX);
+ MESSAGE_BOX_MAP.put(Mms.Sent.CONTENT_URI, Mms.MESSAGE_BOX_SENT);
+ MESSAGE_BOX_MAP.put(Mms.Draft.CONTENT_URI, Mms.MESSAGE_BOX_DRAFTS);
+ MESSAGE_BOX_MAP.put(Mms.Outbox.CONTENT_URI, Mms.MESSAGE_BOX_OUTBOX);
+
+ CHARSET_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>();
+ CHARSET_COLUMN_INDEX_MAP.put(PduHeaders.SUBJECT, PDU_COLUMN_SUBJECT_CHARSET);
+ CHARSET_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_TEXT, PDU_COLUMN_RETRIEVE_TEXT_CHARSET);
+
+ CHARSET_COLUMN_NAME_MAP = new HashMap<Integer, String>();
+ CHARSET_COLUMN_NAME_MAP.put(PduHeaders.SUBJECT, Mms.SUBJECT_CHARSET);
+ CHARSET_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_TEXT, Mms.RETRIEVE_TEXT_CHARSET);
+
+ // Encoded string field code -> column index/name map.
+ ENCODED_STRING_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>();
+ ENCODED_STRING_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_TEXT, PDU_COLUMN_RETRIEVE_TEXT);
+ ENCODED_STRING_COLUMN_INDEX_MAP.put(PduHeaders.SUBJECT, PDU_COLUMN_SUBJECT);
+
+ ENCODED_STRING_COLUMN_NAME_MAP = new HashMap<Integer, String>();
+ ENCODED_STRING_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_TEXT, Mms.RETRIEVE_TEXT);
+ ENCODED_STRING_COLUMN_NAME_MAP.put(PduHeaders.SUBJECT, Mms.SUBJECT);
+
+ // Text string field code -> column index/name map.
+ TEXT_STRING_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>();
+ TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_LOCATION, PDU_COLUMN_CONTENT_LOCATION);
+ TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_TYPE, PDU_COLUMN_CONTENT_TYPE);
+ TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_CLASS, PDU_COLUMN_MESSAGE_CLASS);
+ TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_ID, PDU_COLUMN_MESSAGE_ID);
+ TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.RESPONSE_TEXT, PDU_COLUMN_RESPONSE_TEXT);
+ TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.TRANSACTION_ID, PDU_COLUMN_TRANSACTION_ID);
+
+ TEXT_STRING_COLUMN_NAME_MAP = new HashMap<Integer, String>();
+ TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_LOCATION, Mms.CONTENT_LOCATION);
+ TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_TYPE, Mms.CONTENT_TYPE);
+ TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_CLASS, Mms.MESSAGE_CLASS);
+ TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_ID, Mms.MESSAGE_ID);
+ TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.RESPONSE_TEXT, Mms.RESPONSE_TEXT);
+ TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.TRANSACTION_ID, Mms.TRANSACTION_ID);
+
+ // Octet field code -> column index/name map.
+ OCTET_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>();
+ OCTET_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_CLASS, PDU_COLUMN_CONTENT_CLASS);
+ OCTET_COLUMN_INDEX_MAP.put(PduHeaders.DELIVERY_REPORT, PDU_COLUMN_DELIVERY_REPORT);
+ OCTET_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_TYPE, PDU_COLUMN_MESSAGE_TYPE);
+ OCTET_COLUMN_INDEX_MAP.put(PduHeaders.MMS_VERSION, PDU_COLUMN_MMS_VERSION);
+ OCTET_COLUMN_INDEX_MAP.put(PduHeaders.PRIORITY, PDU_COLUMN_PRIORITY);
+ OCTET_COLUMN_INDEX_MAP.put(PduHeaders.READ_REPORT, PDU_COLUMN_READ_REPORT);
+ OCTET_COLUMN_INDEX_MAP.put(PduHeaders.READ_STATUS, PDU_COLUMN_READ_STATUS);
+ OCTET_COLUMN_INDEX_MAP.put(PduHeaders.REPORT_ALLOWED, PDU_COLUMN_REPORT_ALLOWED);
+ OCTET_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_STATUS, PDU_COLUMN_RETRIEVE_STATUS);
+ OCTET_COLUMN_INDEX_MAP.put(PduHeaders.STATUS, PDU_COLUMN_STATUS);
+
+ OCTET_COLUMN_NAME_MAP = new HashMap<Integer, String>();
+ OCTET_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_CLASS, Mms.CONTENT_CLASS);
+ OCTET_COLUMN_NAME_MAP.put(PduHeaders.DELIVERY_REPORT, Mms.DELIVERY_REPORT);
+ OCTET_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_TYPE, Mms.MESSAGE_TYPE);
+ OCTET_COLUMN_NAME_MAP.put(PduHeaders.MMS_VERSION, Mms.MMS_VERSION);
+ OCTET_COLUMN_NAME_MAP.put(PduHeaders.PRIORITY, Mms.PRIORITY);
+ OCTET_COLUMN_NAME_MAP.put(PduHeaders.READ_REPORT, Mms.READ_REPORT);
+ OCTET_COLUMN_NAME_MAP.put(PduHeaders.READ_STATUS, Mms.READ_STATUS);
+ OCTET_COLUMN_NAME_MAP.put(PduHeaders.REPORT_ALLOWED, Mms.REPORT_ALLOWED);
+ OCTET_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_STATUS, Mms.RETRIEVE_STATUS);
+ OCTET_COLUMN_NAME_MAP.put(PduHeaders.STATUS, Mms.STATUS);
+
+ // Long field code -> column index/name map.
+ LONG_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>();
+ LONG_COLUMN_INDEX_MAP.put(PduHeaders.DATE, PDU_COLUMN_DATE);
+ LONG_COLUMN_INDEX_MAP.put(PduHeaders.DELIVERY_TIME, PDU_COLUMN_DELIVERY_TIME);
+ LONG_COLUMN_INDEX_MAP.put(PduHeaders.EXPIRY, PDU_COLUMN_EXPIRY);
+ LONG_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_SIZE, PDU_COLUMN_MESSAGE_SIZE);
+
+ LONG_COLUMN_NAME_MAP = new HashMap<Integer, String>();
+ LONG_COLUMN_NAME_MAP.put(PduHeaders.DATE, Mms.DATE);
+ LONG_COLUMN_NAME_MAP.put(PduHeaders.DELIVERY_TIME, Mms.DELIVERY_TIME);
+ LONG_COLUMN_NAME_MAP.put(PduHeaders.EXPIRY, Mms.EXPIRY);
+ LONG_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_SIZE, Mms.MESSAGE_SIZE);
+
+ PDU_CACHE_INSTANCE = PduCache.getInstance();
+ }
+
+ @UnsupportedAppUsage
+ private final Context mContext;
+ @UnsupportedAppUsage
+ private final ContentResolver mContentResolver;
+ private final DrmManagerClient mDrmManagerClient;
+ @UnsupportedAppUsage
+ private final TelephonyManager mTelephonyManager;
+
+ private PduPersister(Context context) {
+ mContext = context;
+ mContentResolver = context.getContentResolver();
+ mDrmManagerClient = new DrmManagerClient(context);
+ mTelephonyManager = (TelephonyManager)context
+ .getSystemService(Context.TELEPHONY_SERVICE);
+ }
+
+ /** Get(or create if not exist) an instance of PduPersister */
+ @UnsupportedAppUsage
+ public static PduPersister getPduPersister(Context context) {
+ if ((sPersister == null)) {
+ sPersister = new PduPersister(context);
+ } else if (!context.equals(sPersister.mContext)) {
+ sPersister.release();
+ sPersister = new PduPersister(context);
+ }
+
+ return sPersister;
+ }
+
+ private void setEncodedStringValueToHeaders(
+ Cursor c, int columnIndex,
+ PduHeaders headers, int mapColumn) {
+ String s = c.getString(columnIndex);
+ if ((s != null) && (s.length() > 0)) {
+ int charsetColumnIndex = CHARSET_COLUMN_INDEX_MAP.get(mapColumn);
+ int charset = c.getInt(charsetColumnIndex);
+ EncodedStringValue value = new EncodedStringValue(
+ charset, getBytes(s));
+ headers.setEncodedStringValue(value, mapColumn);
+ }
+ }
+
+ private void setTextStringToHeaders(
+ Cursor c, int columnIndex,
+ PduHeaders headers, int mapColumn) {
+ String s = c.getString(columnIndex);
+ if (s != null) {
+ headers.setTextString(getBytes(s), mapColumn);
+ }
+ }
+
+ private void setOctetToHeaders(
+ Cursor c, int columnIndex,
+ PduHeaders headers, int mapColumn) throws InvalidHeaderValueException {
+ if (!c.isNull(columnIndex)) {
+ int b = c.getInt(columnIndex);
+ headers.setOctet(b, mapColumn);
+ }
+ }
+
+ private void setLongToHeaders(
+ Cursor c, int columnIndex,
+ PduHeaders headers, int mapColumn) {
+ if (!c.isNull(columnIndex)) {
+ long l = c.getLong(columnIndex);
+ headers.setLongInteger(l, mapColumn);
+ }
+ }
+
+ @UnsupportedAppUsage
+ private Integer getIntegerFromPartColumn(Cursor c, int columnIndex) {
+ if (!c.isNull(columnIndex)) {
+ return c.getInt(columnIndex);
+ }
+ return null;
+ }
+
+ @UnsupportedAppUsage
+ private byte[] getByteArrayFromPartColumn(Cursor c, int columnIndex) {
+ if (!c.isNull(columnIndex)) {
+ return getBytes(c.getString(columnIndex));
+ }
+ return null;
+ }
+
+ private PduPart[] loadParts(long msgId) throws MmsException {
+ Cursor c = SqliteWrapper.query(mContext, mContentResolver,
+ Uri.parse("content://mms/" + msgId + "/part"),
+ PART_PROJECTION, null, null, null);
+
+ PduPart[] parts = null;
+
+ try {
+ if ((c == null) || (c.getCount() == 0)) {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "loadParts(" + msgId + "): no part to load.");
+ }
+ return null;
+ }
+
+ int partCount = c.getCount();
+ int partIdx = 0;
+ parts = new PduPart[partCount];
+ while (c.moveToNext()) {
+ PduPart part = new PduPart();
+ Integer charset = getIntegerFromPartColumn(
+ c, PART_COLUMN_CHARSET);
+ if (charset != null) {
+ part.setCharset(charset);
+ }
+
+ byte[] contentDisposition = getByteArrayFromPartColumn(
+ c, PART_COLUMN_CONTENT_DISPOSITION);
+ if (contentDisposition != null) {
+ part.setContentDisposition(contentDisposition);
+ }
+
+ byte[] contentId = getByteArrayFromPartColumn(
+ c, PART_COLUMN_CONTENT_ID);
+ if (contentId != null) {
+ part.setContentId(contentId);
+ }
+
+ byte[] contentLocation = getByteArrayFromPartColumn(
+ c, PART_COLUMN_CONTENT_LOCATION);
+ if (contentLocation != null) {
+ part.setContentLocation(contentLocation);
+ }
+
+ byte[] contentType = getByteArrayFromPartColumn(
+ c, PART_COLUMN_CONTENT_TYPE);
+ if (contentType != null) {
+ part.setContentType(contentType);
+ } else {
+ throw new MmsException("Content-Type must be set.");
+ }
+
+ byte[] fileName = getByteArrayFromPartColumn(
+ c, PART_COLUMN_FILENAME);
+ if (fileName != null) {
+ part.setFilename(fileName);
+ }
+
+ byte[] name = getByteArrayFromPartColumn(
+ c, PART_COLUMN_NAME);
+ if (name != null) {
+ part.setName(name);
+ }
+
+ // Construct a Uri for this part.
+ long partId = c.getLong(PART_COLUMN_ID);
+ Uri partURI = Uri.parse("content://mms/part/" + partId);
+ part.setDataUri(partURI);
+
+ // For images/audio/video, we won't keep their data in Part
+ // because their renderer accept Uri as source.
+ String type = toIsoString(contentType);
+ if (!ContentType.isImageType(type)
+ && !ContentType.isAudioType(type)
+ && !ContentType.isVideoType(type)) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ InputStream is = null;
+
+ // Store simple string values directly in the database instead of an
+ // external file. This makes the text searchable and retrieval slightly
+ // faster.
+ if (ContentType.TEXT_PLAIN.equals(type) || ContentType.APP_SMIL.equals(type)
+ || ContentType.TEXT_HTML.equals(type)) {
+ String text = c.getString(PART_COLUMN_TEXT);
+ byte [] blob = new EncodedStringValue(text != null ? text : "")
+ .getTextString();
+ baos.write(blob, 0, blob.length);
+ } else {
+
+ try {
+ is = mContentResolver.openInputStream(partURI);
+
+ byte[] buffer = new byte[256];
+ int len = is.read(buffer);
+ while (len >= 0) {
+ baos.write(buffer, 0, len);
+ len = is.read(buffer);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to load part data", e);
+ c.close();
+ throw new MmsException(e);
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to close stream", e);
+ } // Ignore
+ }
+ }
+ }
+ part.setData(baos.toByteArray());
+ }
+ parts[partIdx++] = part;
+ }
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+
+ return parts;
+ }
+
+ private void loadAddress(long msgId, PduHeaders headers) {
+ Cursor c = SqliteWrapper.query(mContext, mContentResolver,
+ Uri.parse("content://mms/" + msgId + "/addr"),
+ new String[] { Addr.ADDRESS, Addr.CHARSET, Addr.TYPE },
+ null, null, null);
+
+ if (c != null) {
+ try {
+ while (c.moveToNext()) {
+ String addr = c.getString(0);
+ if (!TextUtils.isEmpty(addr)) {
+ int addrType = c.getInt(2);
+ switch (addrType) {
+ case PduHeaders.FROM:
+ headers.setEncodedStringValue(
+ new EncodedStringValue(c.getInt(1), getBytes(addr)),
+ addrType);
+ break;
+ case PduHeaders.TO:
+ case PduHeaders.CC:
+ case PduHeaders.BCC:
+ headers.appendEncodedStringValue(
+ new EncodedStringValue(c.getInt(1), getBytes(addr)),
+ addrType);
+ break;
+ default:
+ Log.e(TAG, "Unknown address type: " + addrType);
+ break;
+ }
+ }
+ }
+ } finally {
+ c.close();
+ }
+ }
+ }
+
+ /**
+ * Load a PDU from storage by given Uri.
+ *
+ * @param uri The Uri of the PDU to be loaded.
+ * @return A generic PDU object, it may be cast to dedicated PDU.
+ * @throws MmsException Failed to load some fields of a PDU.
+ */
+ @UnsupportedAppUsage
+ public GenericPdu load(Uri uri) throws MmsException {
+ GenericPdu pdu = null;
+ PduCacheEntry cacheEntry = null;
+ int msgBox = 0;
+ long threadId = -1;
+ try {
+ synchronized(PDU_CACHE_INSTANCE) {
+ if (PDU_CACHE_INSTANCE.isUpdating(uri)) {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "load: " + uri + " blocked by isUpdating()");
+ }
+ try {
+ PDU_CACHE_INSTANCE.wait();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "load: ", e);
+ }
+ cacheEntry = PDU_CACHE_INSTANCE.get(uri);
+ if (cacheEntry != null) {
+ return cacheEntry.getPdu();
+ }
+ }
+ // Tell the cache to indicate to other callers that this item
+ // is currently being updated.
+ PDU_CACHE_INSTANCE.setUpdating(uri, true);
+ }
+
+ Cursor c = SqliteWrapper.query(mContext, mContentResolver, uri,
+ PDU_PROJECTION, null, null, null);
+ PduHeaders headers = new PduHeaders();
+ Set<Entry<Integer, Integer>> set;
+ long msgId = ContentUris.parseId(uri);
+
+ try {
+ if ((c == null) || (c.getCount() != 1) || !c.moveToFirst()) {
+ throw new MmsException("Bad uri: " + uri);
+ }
+
+ msgBox = c.getInt(PDU_COLUMN_MESSAGE_BOX);
+ threadId = c.getLong(PDU_COLUMN_THREAD_ID);
+
+ set = ENCODED_STRING_COLUMN_INDEX_MAP.entrySet();
+ for (Entry<Integer, Integer> e : set) {
+ setEncodedStringValueToHeaders(
+ c, e.getValue(), headers, e.getKey());
+ }
+
+ set = TEXT_STRING_COLUMN_INDEX_MAP.entrySet();
+ for (Entry<Integer, Integer> e : set) {
+ setTextStringToHeaders(
+ c, e.getValue(), headers, e.getKey());
+ }
+
+ set = OCTET_COLUMN_INDEX_MAP.entrySet();
+ for (Entry<Integer, Integer> e : set) {
+ setOctetToHeaders(
+ c, e.getValue(), headers, e.getKey());
+ }
+
+ set = LONG_COLUMN_INDEX_MAP.entrySet();
+ for (Entry<Integer, Integer> e : set) {
+ setLongToHeaders(
+ c, e.getValue(), headers, e.getKey());
+ }
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+
+ // Check whether 'msgId' has been assigned a valid value.
+ if (msgId == -1L) {
+ throw new MmsException("Error! ID of the message: -1.");
+ }
+
+ // Load address information of the MM.
+ loadAddress(msgId, headers);
+
+ int msgType = headers.getOctet(PduHeaders.MESSAGE_TYPE);
+ PduBody body = new PduBody();
+
+ // For PDU which type is M_retrieve.conf or Send.req, we should
+ // load multiparts and put them into the body of the PDU.
+ if ((msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF)
+ || (msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)) {
+ PduPart[] parts = loadParts(msgId);
+ if (parts != null) {
+ int partsNum = parts.length;
+ for (int i = 0; i < partsNum; i++) {
+ body.addPart(parts[i]);
+ }
+ }
+ }
+
+ switch (msgType) {
+ case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
+ pdu = new NotificationInd(headers);
+ break;
+ case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
+ pdu = new DeliveryInd(headers);
+ break;
+ case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
+ pdu = new ReadOrigInd(headers);
+ break;
+ case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
+ pdu = new RetrieveConf(headers, body);
+ break;
+ case PduHeaders.MESSAGE_TYPE_SEND_REQ:
+ pdu = new SendReq(headers, body);
+ break;
+ case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
+ pdu = new AcknowledgeInd(headers);
+ break;
+ case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
+ pdu = new NotifyRespInd(headers);
+ break;
+ case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
+ pdu = new ReadRecInd(headers);
+ break;
+ case PduHeaders.MESSAGE_TYPE_SEND_CONF:
+ case PduHeaders.MESSAGE_TYPE_FORWARD_REQ:
+ case PduHeaders.MESSAGE_TYPE_FORWARD_CONF:
+ case PduHeaders.MESSAGE_TYPE_MBOX_STORE_REQ:
+ case PduHeaders.MESSAGE_TYPE_MBOX_STORE_CONF:
+ case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_REQ:
+ case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_CONF:
+ case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_REQ:
+ case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_CONF:
+ case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_REQ:
+ case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_CONF:
+ case PduHeaders.MESSAGE_TYPE_MBOX_DESCR:
+ case PduHeaders.MESSAGE_TYPE_DELETE_REQ:
+ case PduHeaders.MESSAGE_TYPE_DELETE_CONF:
+ case PduHeaders.MESSAGE_TYPE_CANCEL_REQ:
+ case PduHeaders.MESSAGE_TYPE_CANCEL_CONF:
+ throw new MmsException(
+ "Unsupported PDU type: " + Integer.toHexString(msgType));
+
+ default:
+ throw new MmsException(
+ "Unrecognized PDU type: " + Integer.toHexString(msgType));
+ }
+ } finally {
+ synchronized(PDU_CACHE_INSTANCE) {
+ if (pdu != null) {
+ assert(PDU_CACHE_INSTANCE.get(uri) == null);
+ // Update the cache entry with the real info
+ cacheEntry = new PduCacheEntry(pdu, msgBox, threadId);
+ PDU_CACHE_INSTANCE.put(uri, cacheEntry);
+ }
+ PDU_CACHE_INSTANCE.setUpdating(uri, false);
+ PDU_CACHE_INSTANCE.notifyAll(); // tell anybody waiting on this entry to go ahead
+ }
+ }
+ return pdu;
+ }
+
+ @UnsupportedAppUsage
+ private void persistAddress(
+ long msgId, int type, EncodedStringValue[] array) {
+ ContentValues values = new ContentValues(3);
+
+ for (EncodedStringValue addr : array) {
+ values.clear(); // Clear all values first.
+ values.put(Addr.ADDRESS, toIsoString(addr.getTextString()));
+ values.put(Addr.CHARSET, addr.getCharacterSet());
+ values.put(Addr.TYPE, type);
+
+ Uri uri = Uri.parse("content://mms/" + msgId + "/addr");
+ SqliteWrapper.insert(mContext, mContentResolver, uri, values);
+ }
+ }
+
+ @UnsupportedAppUsage
+ private static String getPartContentType(PduPart part) {
+ return part.getContentType() == null ? null : toIsoString(part.getContentType());
+ }
+
+ @UnsupportedAppUsage
+ public Uri persistPart(PduPart part, long msgId, HashMap<Uri, InputStream> preOpenedFiles)
+ throws MmsException {
+ Uri uri = Uri.parse("content://mms/" + msgId + "/part");
+ ContentValues values = new ContentValues(8);
+
+ int charset = part.getCharset();
+ if (charset != 0 ) {
+ values.put(Part.CHARSET, charset);
+ }
+
+ String contentType = getPartContentType(part);
+ if (contentType != null) {
+ // There is no "image/jpg" in Android (and it's an invalid mimetype).
+ // Change it to "image/jpeg"
+ if (ContentType.IMAGE_JPG.equals(contentType)) {
+ contentType = ContentType.IMAGE_JPEG;
+ }
+
+ values.put(Part.CONTENT_TYPE, contentType);
+ // To ensure the SMIL part is always the first part.
+ if (ContentType.APP_SMIL.equals(contentType)) {
+ values.put(Part.SEQ, -1);
+ }
+ } else {
+ throw new MmsException("MIME type of the part must be set.");
+ }
+
+ if (part.getFilename() != null) {
+ String fileName = new String(part.getFilename());
+ values.put(Part.FILENAME, fileName);
+ }
+
+ if (part.getName() != null) {
+ String name = new String(part.getName());
+ values.put(Part.NAME, name);
+ }
+
+ Object value = null;
+ if (part.getContentDisposition() != null) {
+ value = toIsoString(part.getContentDisposition());
+ values.put(Part.CONTENT_DISPOSITION, (String) value);
+ }
+
+ if (part.getContentId() != null) {
+ value = toIsoString(part.getContentId());
+ values.put(Part.CONTENT_ID, (String) value);
+ }
+
+ if (part.getContentLocation() != null) {
+ value = toIsoString(part.getContentLocation());
+ values.put(Part.CONTENT_LOCATION, (String) value);
+ }
+
+ Uri res = SqliteWrapper.insert(mContext, mContentResolver, uri, values);
+ if (res == null) {
+ throw new MmsException("Failed to persist part, return null.");
+ }
+
+ persistData(part, res, contentType, preOpenedFiles);
+ // After successfully store the data, we should update
+ // the dataUri of the part.
+ part.setDataUri(res);
+
+ return res;
+ }
+
+ /**
+ * Save data of the part into storage. The source data may be given
+ * by a byte[] or a Uri. If it's a byte[], directly save it
+ * into storage, otherwise load source data from the dataUri and then
+ * save it. If the data is an image, we may scale down it according
+ * to user preference.
+ *
+ * @param part The PDU part which contains data to be saved.
+ * @param uri The URI of the part.
+ * @param contentType The MIME type of the part.
+ * @param preOpenedFiles if not null, a map of preopened InputStreams for the parts.
+ * @throws MmsException Cannot find source data or error occurred
+ * while saving the data.
+ */
+ private void persistData(PduPart part, Uri uri,
+ String contentType, HashMap<Uri, InputStream> preOpenedFiles)
+ throws MmsException {
+ OutputStream os = null;
+ InputStream is = null;
+ DrmConvertSession drmConvertSession = null;
+ Uri dataUri = null;
+ String path = null;
+
+ try {
+ byte[] data = part.getData();
+ if (ContentType.TEXT_PLAIN.equals(contentType)
+ || ContentType.APP_SMIL.equals(contentType)
+ || ContentType.TEXT_HTML.equals(contentType)) {
+ ContentValues cv = new ContentValues();
+ if (data == null) {
+ data = new String("").getBytes(CharacterSets.DEFAULT_CHARSET_NAME);
+ }
+ cv.put(Telephony.Mms.Part.TEXT, new EncodedStringValue(data).getString());
+ if (mContentResolver.update(uri, cv, null, null) != 1) {
+ throw new MmsException("unable to update " + uri.toString());
+ }
+ } else {
+ boolean isDrm = DownloadDrmHelper.isDrmConvertNeeded(contentType);
+ if (isDrm) {
+ if (uri != null) {
+ try (ParcelFileDescriptor pfd =
+ mContentResolver.openFileDescriptor(uri, "r")) {
+ if (pfd.getStatSize() > 0) {
+ // we're not going to re-persist and re-encrypt an already
+ // converted drm file
+ return;
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Can't get file info for: " + part.getDataUri(), e);
+ }
+ }
+ // We haven't converted the file yet, start the conversion
+ drmConvertSession = DrmConvertSession.open(mContext, contentType);
+ if (drmConvertSession == null) {
+ throw new MmsException("Mimetype " + contentType +
+ " can not be converted.");
+ }
+ }
+ // uri can look like:
+ // content://mms/part/98
+ os = mContentResolver.openOutputStream(uri);
+ if (data == null) {
+ dataUri = part.getDataUri();
+ if ((dataUri == null) || (dataUri.equals(uri))) {
+ Log.w(TAG, "Can't find data for this part.");
+ return;
+ }
+ // dataUri can look like:
+ // content://com.google.android.gallery3d.provider/picasa/item/5720646660183715586
+ if (preOpenedFiles != null && preOpenedFiles.containsKey(dataUri)) {
+ is = preOpenedFiles.get(dataUri);
+ }
+ if (is == null) {
+ is = mContentResolver.openInputStream(dataUri);
+ }
+
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "Saving data to: " + uri);
+ }
+
+ byte[] buffer = new byte[8192];
+ for (int len = 0; (len = is.read(buffer)) != -1; ) {
+ if (!isDrm) {
+ os.write(buffer, 0, len);
+ } else {
+ byte[] convertedData = drmConvertSession.convert(buffer, len);
+ if (convertedData != null) {
+ os.write(convertedData, 0, convertedData.length);
+ } else {
+ throw new MmsException("Error converting drm data.");
+ }
+ }
+ }
+ } else {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "Saving data to: " + uri);
+ }
+ if (!isDrm) {
+ os.write(data);
+ } else {
+ dataUri = uri;
+ byte[] convertedData = drmConvertSession.convert(data, data.length);
+ if (convertedData != null) {
+ os.write(convertedData, 0, convertedData.length);
+ } else {
+ throw new MmsException("Error converting drm data.");
+ }
+ }
+ }
+ }
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Failed to open Input/Output stream.", e);
+ throw new MmsException(e);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to read/write data.", e);
+ throw new MmsException(e);
+ } finally {
+ if (os != null) {
+ try {
+ os.close();
+ } catch (IOException e) {
+ Log.e(TAG, "IOException while closing: " + os, e);
+ } // Ignore
+ }
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ Log.e(TAG, "IOException while closing: " + is, e);
+ } // Ignore
+ }
+ if (drmConvertSession != null) {
+ drmConvertSession.close(path);
+
+ // Reset the permissions on the encrypted part file so everyone has only read
+ // permission.
+ File f = new File(path);
+ ContentValues values = new ContentValues(0);
+ SqliteWrapper.update(mContext, mContentResolver,
+ Uri.parse("content://mms/resetFilePerm/" + f.getName()),
+ values, null, null);
+ }
+ }
+ }
+
+ @UnsupportedAppUsage
+ private void updateAddress(
+ long msgId, int type, EncodedStringValue[] array) {
+ // Delete old address information and then insert new ones.
+ SqliteWrapper.delete(mContext, mContentResolver,
+ Uri.parse("content://mms/" + msgId + "/addr"),
+ Addr.TYPE + "=" + type, null);
+
+ persistAddress(msgId, type, array);
+ }
+
+ /**
+ * Update headers of a SendReq.
+ *
+ * @param uri The PDU which need to be updated.
+ * @param pdu New headers.
+ * @throws MmsException Bad URI or updating failed.
+ */
+ @UnsupportedAppUsage
+ public void updateHeaders(Uri uri, SendReq sendReq) {
+ synchronized(PDU_CACHE_INSTANCE) {
+ // If the cache item is getting updated, wait until it's done updating before
+ // purging it.
+ if (PDU_CACHE_INSTANCE.isUpdating(uri)) {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "updateHeaders: " + uri + " blocked by isUpdating()");
+ }
+ try {
+ PDU_CACHE_INSTANCE.wait();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "updateHeaders: ", e);
+ }
+ }
+ }
+ PDU_CACHE_INSTANCE.purge(uri);
+
+ ContentValues values = new ContentValues(10);
+ byte[] contentType = sendReq.getContentType();
+ if (contentType != null) {
+ values.put(Mms.CONTENT_TYPE, toIsoString(contentType));
+ }
+
+ long date = sendReq.getDate();
+ if (date != -1) {
+ values.put(Mms.DATE, date);
+ }
+
+ int deliveryReport = sendReq.getDeliveryReport();
+ if (deliveryReport != 0) {
+ values.put(Mms.DELIVERY_REPORT, deliveryReport);
+ }
+
+ long expiry = sendReq.getExpiry();
+ if (expiry != -1) {
+ values.put(Mms.EXPIRY, expiry);
+ }
+
+ byte[] msgClass = sendReq.getMessageClass();
+ if (msgClass != null) {
+ values.put(Mms.MESSAGE_CLASS, toIsoString(msgClass));
+ }
+
+ int priority = sendReq.getPriority();
+ if (priority != 0) {
+ values.put(Mms.PRIORITY, priority);
+ }
+
+ int readReport = sendReq.getReadReport();
+ if (readReport != 0) {
+ values.put(Mms.READ_REPORT, readReport);
+ }
+
+ byte[] transId = sendReq.getTransactionId();
+ if (transId != null) {
+ values.put(Mms.TRANSACTION_ID, toIsoString(transId));
+ }
+
+ EncodedStringValue subject = sendReq.getSubject();
+ if (subject != null) {
+ values.put(Mms.SUBJECT, toIsoString(subject.getTextString()));
+ values.put(Mms.SUBJECT_CHARSET, subject.getCharacterSet());
+ } else {
+ values.put(Mms.SUBJECT, "");
+ }
+
+ long messageSize = sendReq.getMessageSize();
+ if (messageSize > 0) {
+ values.put(Mms.MESSAGE_SIZE, messageSize);
+ }
+
+ PduHeaders headers = sendReq.getPduHeaders();
+ HashSet<String> recipients = new HashSet<String>();
+ for (int addrType : ADDRESS_FIELDS) {
+ EncodedStringValue[] array = null;
+ if (addrType == PduHeaders.FROM) {
+ EncodedStringValue v = headers.getEncodedStringValue(addrType);
+ if (v != null) {
+ array = new EncodedStringValue[1];
+ array[0] = v;
+ }
+ } else {
+ array = headers.getEncodedStringValues(addrType);
+ }
+
+ if (array != null) {
+ long msgId = ContentUris.parseId(uri);
+ updateAddress(msgId, addrType, array);
+ if (addrType == PduHeaders.TO) {
+ for (EncodedStringValue v : array) {
+ if (v != null) {
+ recipients.add(v.getString());
+ }
+ }
+ }
+ }
+ }
+ if (!recipients.isEmpty()) {
+ long threadId = Threads.getOrCreateThreadId(mContext, recipients);
+ values.put(Mms.THREAD_ID, threadId);
+ }
+
+ SqliteWrapper.update(mContext, mContentResolver, uri, values, null, null);
+ }
+
+ private void updatePart(Uri uri, PduPart part, HashMap<Uri, InputStream> preOpenedFiles)
+ throws MmsException {
+ ContentValues values = new ContentValues(7);
+
+ int charset = part.getCharset();
+ if (charset != 0 ) {
+ values.put(Part.CHARSET, charset);
+ }
+
+ String contentType = null;
+ if (part.getContentType() != null) {
+ contentType = toIsoString(part.getContentType());
+ values.put(Part.CONTENT_TYPE, contentType);
+ } else {
+ throw new MmsException("MIME type of the part must be set.");
+ }
+
+ if (part.getFilename() != null) {
+ String fileName = new String(part.getFilename());
+ values.put(Part.FILENAME, fileName);
+ }
+
+ if (part.getName() != null) {
+ String name = new String(part.getName());
+ values.put(Part.NAME, name);
+ }
+
+ Object value = null;
+ if (part.getContentDisposition() != null) {
+ value = toIsoString(part.getContentDisposition());
+ values.put(Part.CONTENT_DISPOSITION, (String) value);
+ }
+
+ if (part.getContentId() != null) {
+ value = toIsoString(part.getContentId());
+ values.put(Part.CONTENT_ID, (String) value);
+ }
+
+ if (part.getContentLocation() != null) {
+ value = toIsoString(part.getContentLocation());
+ values.put(Part.CONTENT_LOCATION, (String) value);
+ }
+
+ SqliteWrapper.update(mContext, mContentResolver, uri, values, null, null);
+
+ // Only update the data when:
+ // 1. New binary data supplied or
+ // 2. The Uri of the part is different from the current one.
+ if ((part.getData() != null)
+ || (!uri.equals(part.getDataUri()))) {
+ persistData(part, uri, contentType, preOpenedFiles);
+ }
+ }
+
+ /**
+ * Update all parts of a PDU.
+ *
+ * @param uri The PDU which need to be updated.
+ * @param body New message body of the PDU.
+ * @param preOpenedFiles if not null, a map of preopened InputStreams for the parts.
+ * @throws MmsException Bad URI or updating failed.
+ */
+ @UnsupportedAppUsage
+ public void updateParts(Uri uri, PduBody body, HashMap<Uri, InputStream> preOpenedFiles)
+ throws MmsException {
+ try {
+ PduCacheEntry cacheEntry;
+ synchronized(PDU_CACHE_INSTANCE) {
+ if (PDU_CACHE_INSTANCE.isUpdating(uri)) {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "updateParts: " + uri + " blocked by isUpdating()");
+ }
+ try {
+ PDU_CACHE_INSTANCE.wait();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "updateParts: ", e);
+ }
+ cacheEntry = PDU_CACHE_INSTANCE.get(uri);
+ if (cacheEntry != null) {
+ ((MultimediaMessagePdu) cacheEntry.getPdu()).setBody(body);
+ }
+ }
+ // Tell the cache to indicate to other callers that this item
+ // is currently being updated.
+ PDU_CACHE_INSTANCE.setUpdating(uri, true);
+ }
+
+ ArrayList<PduPart> toBeCreated = new ArrayList<PduPart>();
+ HashMap<Uri, PduPart> toBeUpdated = new HashMap<Uri, PduPart>();
+
+ int partsNum = body.getPartsNum();
+ StringBuilder filter = new StringBuilder().append('(');
+ for (int i = 0; i < partsNum; i++) {
+ PduPart part = body.getPart(i);
+ Uri partUri = part.getDataUri();
+ if ((partUri == null) || TextUtils.isEmpty(partUri.getAuthority())
+ || !partUri.getAuthority().startsWith("mms")) {
+ toBeCreated.add(part);
+ } else {
+ toBeUpdated.put(partUri, part);
+
+ // Don't use 'i > 0' to determine whether we should append
+ // 'AND' since 'i = 0' may be skipped in another branch.
+ if (filter.length() > 1) {
+ filter.append(" AND ");
+ }
+
+ filter.append(Part._ID);
+ filter.append("!=");
+ DatabaseUtils.appendEscapedSQLString(filter, partUri.getLastPathSegment());
+ }
+ }
+ filter.append(')');
+
+ long msgId = ContentUris.parseId(uri);
+
+ // Remove the parts which doesn't exist anymore.
+ SqliteWrapper.delete(mContext, mContentResolver,
+ Uri.parse(Mms.CONTENT_URI + "/" + msgId + "/part"),
+ filter.length() > 2 ? filter.toString() : null, null);
+
+ // Create new parts which didn't exist before.
+ for (PduPart part : toBeCreated) {
+ persistPart(part, msgId, preOpenedFiles);
+ }
+
+ // Update the modified parts.
+ for (Map.Entry<Uri, PduPart> e : toBeUpdated.entrySet()) {
+ updatePart(e.getKey(), e.getValue(), preOpenedFiles);
+ }
+ } finally {
+ synchronized(PDU_CACHE_INSTANCE) {
+ PDU_CACHE_INSTANCE.setUpdating(uri, false);
+ PDU_CACHE_INSTANCE.notifyAll();
+ }
+ }
+ }
+
+ /**
+ * Persist a PDU object to specific location in the storage.
+ *
+ * @param pdu The PDU object to be stored.
+ * @param uri Where to store the given PDU object.
+ * @param createThreadId if true, this function may create a thread id for the recipients
+ * @param groupMmsEnabled if true, all of the recipients addressed in the PDU will be used
+ * to create the associated thread. When false, only the sender will be used in finding or
+ * creating the appropriate thread or conversation.
+ * @param preOpenedFiles if not null, a map of preopened InputStreams for the parts.
+ * @return A Uri which can be used to access the stored PDU.
+ */
+
+ @UnsupportedAppUsage
+ public Uri persist(GenericPdu pdu, Uri uri, boolean createThreadId, boolean groupMmsEnabled,
+ HashMap<Uri, InputStream> preOpenedFiles)
+ throws MmsException {
+ if (uri == null) {
+ throw new MmsException("Uri may not be null.");
+ }
+ long msgId = -1;
+ try {
+ msgId = ContentUris.parseId(uri);
+ } catch (NumberFormatException e) {
+ // the uri ends with "inbox" or something else like that
+ }
+ boolean existingUri = msgId != -1;
+
+ if (!existingUri && MESSAGE_BOX_MAP.get(uri) == null) {
+ throw new MmsException(
+ "Bad destination, must be one of "
+ + "content://mms/inbox, content://mms/sent, "
+ + "content://mms/drafts, content://mms/outbox, "
+ + "content://mms/temp.");
+ }
+ synchronized(PDU_CACHE_INSTANCE) {
+ // If the cache item is getting updated, wait until it's done updating before
+ // purging it.
+ if (PDU_CACHE_INSTANCE.isUpdating(uri)) {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "persist: " + uri + " blocked by isUpdating()");
+ }
+ try {
+ PDU_CACHE_INSTANCE.wait();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "persist1: ", e);
+ }
+ }
+ }
+ PDU_CACHE_INSTANCE.purge(uri);
+
+ PduHeaders header = pdu.getPduHeaders();
+ PduBody body = null;
+ ContentValues values = new ContentValues();
+ Set<Entry<Integer, String>> set;
+
+ set = ENCODED_STRING_COLUMN_NAME_MAP.entrySet();
+ for (Entry<Integer, String> e : set) {
+ int field = e.getKey();
+ EncodedStringValue encodedString = header.getEncodedStringValue(field);
+ if (encodedString != null) {
+ String charsetColumn = CHARSET_COLUMN_NAME_MAP.get(field);
+ values.put(e.getValue(), toIsoString(encodedString.getTextString()));
+ values.put(charsetColumn, encodedString.getCharacterSet());
+ }
+ }
+
+ set = TEXT_STRING_COLUMN_NAME_MAP.entrySet();
+ for (Entry<Integer, String> e : set){
+ byte[] text = header.getTextString(e.getKey());
+ if (text != null) {
+ values.put(e.getValue(), toIsoString(text));
+ }
+ }
+
+ set = OCTET_COLUMN_NAME_MAP.entrySet();
+ for (Entry<Integer, String> e : set){
+ int b = header.getOctet(e.getKey());
+ if (b != 0) {
+ values.put(e.getValue(), b);
+ }
+ }
+
+ set = LONG_COLUMN_NAME_MAP.entrySet();
+ for (Entry<Integer, String> e : set){
+ long l = header.getLongInteger(e.getKey());
+ if (l != -1L) {
+ values.put(e.getValue(), l);
+ }
+ }
+
+ HashMap<Integer, EncodedStringValue[]> addressMap =
+ new HashMap<Integer, EncodedStringValue[]>(ADDRESS_FIELDS.length);
+ // Save address information.
+ for (int addrType : ADDRESS_FIELDS) {
+ EncodedStringValue[] array = null;
+ if (addrType == PduHeaders.FROM) {
+ EncodedStringValue v = header.getEncodedStringValue(addrType);
+ if (v != null) {
+ array = new EncodedStringValue[1];
+ array[0] = v;
+ }
+ } else {
+ array = header.getEncodedStringValues(addrType);
+ }
+ addressMap.put(addrType, array);
+ }
+
+ HashSet<String> recipients = new HashSet<String>();
+ int msgType = pdu.getMessageType();
+ // Here we only allocate thread ID for M-Notification.ind,
+ // M-Retrieve.conf and M-Send.req.
+ // Some of other PDU types may be allocated a thread ID outside
+ // this scope.
+ if ((msgType == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND)
+ || (msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF)
+ || (msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)) {
+ switch (msgType) {
+ case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
+ case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
+ loadRecipients(PduHeaders.FROM, recipients, addressMap, false);
+
+ // For received messages when group MMS is enabled, we want to associate this
+ // message with the thread composed of all the recipients -- all but our own
+ // number, that is. This includes the person who sent the
+ // message or the FROM field (above) in addition to the other people the message
+ // was addressed to or the TO field. Our own number is in that TO field and
+ // we have to ignore it in loadRecipients.
+ if (groupMmsEnabled) {
+ loadRecipients(PduHeaders.TO, recipients, addressMap, true);
+
+ // Also load any numbers in the CC field to address group messaging
+ // compatibility issues with devices that place numbers in this field
+ // for group messages.
+ loadRecipients(PduHeaders.CC, recipients, addressMap, true);
+ }
+ break;
+ case PduHeaders.MESSAGE_TYPE_SEND_REQ:
+ loadRecipients(PduHeaders.TO, recipients, addressMap, false);
+ break;
+ }
+ long threadId = 0;
+ if (createThreadId && !recipients.isEmpty()) {
+ // Given all the recipients associated with this message, find (or create) the
+ // correct thread.
+ threadId = Threads.getOrCreateThreadId(mContext, recipients);
+ }
+ values.put(Mms.THREAD_ID, threadId);
+ }
+
+ // Save parts first to avoid inconsistent message is loaded
+ // while saving the parts.
+ long dummyId = System.currentTimeMillis(); // Dummy ID of the msg.
+
+ // Figure out if this PDU is a text-only message
+ boolean textOnly = true;
+
+ // Sum up the total message size
+ int messageSize = 0;
+
+ // Get body if the PDU is a RetrieveConf or SendReq.
+ if (pdu instanceof MultimediaMessagePdu) {
+ body = ((MultimediaMessagePdu) pdu).getBody();
+ // Start saving parts if necessary.
+ if (body != null) {
+ int partsNum = body.getPartsNum();
+ if (partsNum > 2) {
+ // For a text-only message there will be two parts: 1-the SMIL, 2-the text.
+ // Down a few lines below we're checking to make sure we've only got SMIL or
+ // text. We also have to check then we don't have more than two parts.
+ // Otherwise, a slideshow with two text slides would be marked as textOnly.
+ textOnly = false;
+ }
+ for (int i = 0; i < partsNum; i++) {
+ PduPart part = body.getPart(i);
+ messageSize += part.getDataLength();
+ persistPart(part, dummyId, preOpenedFiles);
+
+ // If we've got anything besides text/plain or SMIL part, then we've got
+ // an mms message with some other type of attachment.
+ String contentType = getPartContentType(part);
+ if (contentType != null && !ContentType.APP_SMIL.equals(contentType)
+ && !ContentType.TEXT_PLAIN.equals(contentType)) {
+ textOnly = false;
+ }
+ }
+ }
+ }
+ // Record whether this mms message is a simple plain text or not. This is a hint for the
+ // UI.
+ values.put(Mms.TEXT_ONLY, textOnly ? 1 : 0);
+ // The message-size might already have been inserted when parsing the
+ // PDU header. If not, then we insert the message size as well.
+ if (values.getAsInteger(Mms.MESSAGE_SIZE) == null) {
+ values.put(Mms.MESSAGE_SIZE, messageSize);
+ }
+
+ Uri res = null;
+ if (existingUri) {
+ res = uri;
+ SqliteWrapper.update(mContext, mContentResolver, res, values, null, null);
+ } else {
+ res = SqliteWrapper.insert(mContext, mContentResolver, uri, values);
+ if (res == null) {
+ throw new MmsException("persist() failed: return null.");
+ }
+ // Get the real ID of the PDU and update all parts which were
+ // saved with the dummy ID.
+ msgId = ContentUris.parseId(res);
+ }
+
+ values = new ContentValues(1);
+ values.put(Part.MSG_ID, msgId);
+ SqliteWrapper.update(mContext, mContentResolver,
+ Uri.parse("content://mms/" + dummyId + "/part"),
+ values, null, null);
+ // We should return the longest URI of the persisted PDU, for
+ // example, if input URI is "content://mms/inbox" and the _ID of
+ // persisted PDU is '8', we should return "content://mms/inbox/8"
+ // instead of "content://mms/8".
+ // FIXME: Should the MmsProvider be responsible for this???
+ if (!existingUri) {
+ res = Uri.parse(uri + "/" + msgId);
+ }
+
+ // Save address information.
+ for (int addrType : ADDRESS_FIELDS) {
+ EncodedStringValue[] array = addressMap.get(addrType);
+ if (array != null) {
+ persistAddress(msgId, addrType, array);
+ }
+ }
+
+ return res;
+ }
+
+ /**
+ * For a given address type, extract the recipients from the headers.
+ *
+ * @param addressType can be PduHeaders.FROM, PduHeaders.TO or PduHeaders.CC
+ * @param recipients a HashSet that is loaded with the recipients from the FROM, TO or CC headers
+ * @param addressMap a HashMap of the addresses from the ADDRESS_FIELDS header
+ * @param excludeMyNumber if true, the number of this phone will be excluded from recipients
+ */
+ @UnsupportedAppUsage
+ private void loadRecipients(int addressType, HashSet<String> recipients,
+ HashMap<Integer, EncodedStringValue[]> addressMap, boolean excludeMyNumber) {
+ EncodedStringValue[] array = addressMap.get(addressType);
+ if (array == null) {
+ return;
+ }
+ // If the TO recipients is only a single address, then we can skip loadRecipients when
+ // we're excluding our own number because we know that address is our own.
+ if (excludeMyNumber && array.length == 1) {
+ return;
+ }
+ final SubscriptionManager subscriptionManager = SubscriptionManager.from(mContext);
+ final Set<String> myPhoneNumbers = new HashSet<String>();
+ if (excludeMyNumber) {
+ // Build a list of my phone numbers from the various sims.
+ for (int subid : subscriptionManager.getActiveSubscriptionIdList()) {
+ final String myNumber = mTelephonyManager.getLine1Number(subid);
+ if (myNumber != null) {
+ myPhoneNumbers.add(myNumber);
+ }
+ }
+ }
+
+ for (EncodedStringValue v : array) {
+ if (v != null) {
+ final String number = v.getString();
+ if (excludeMyNumber) {
+ for (final String myNumber : myPhoneNumbers) {
+ if (!PhoneNumberUtils.compare(number, myNumber)
+ && !recipients.contains(number)) {
+ // Only add numbers which aren't my own number.
+ recipients.add(number);
+ break;
+ }
+ }
+ } else if (!recipients.contains(number)){
+ recipients.add(number);
+ }
+ }
+ }
+ }
+
+ /**
+ * Move a PDU object from one location to another.
+ *
+ * @param from Specify the PDU object to be moved.
+ * @param to The destination location, should be one of the following:
+ * "content://mms/inbox", "content://mms/sent",
+ * "content://mms/drafts", "content://mms/outbox",
+ * "content://mms/trash".
+ * @return New Uri of the moved PDU.
+ * @throws MmsException Error occurred while moving the message.
+ */
+ @UnsupportedAppUsage
+ public Uri move(Uri from, Uri to) throws MmsException {
+ // Check whether the 'msgId' has been assigned a valid value.
+ long msgId = ContentUris.parseId(from);
+ if (msgId == -1L) {
+ throw new MmsException("Error! ID of the message: -1.");
+ }
+
+ // Get corresponding int value of destination box.
+ Integer msgBox = MESSAGE_BOX_MAP.get(to);
+ if (msgBox == null) {
+ throw new MmsException(
+ "Bad destination, must be one of "
+ + "content://mms/inbox, content://mms/sent, "
+ + "content://mms/drafts, content://mms/outbox, "
+ + "content://mms/temp.");
+ }
+
+ ContentValues values = new ContentValues(1);
+ values.put(Mms.MESSAGE_BOX, msgBox);
+ SqliteWrapper.update(mContext, mContentResolver, from, values, null, null);
+ return ContentUris.withAppendedId(to, msgId);
+ }
+
+ /**
+ * Wrap a byte[] into a String.
+ */
+ @UnsupportedAppUsage
+ public static String toIsoString(byte[] bytes) {
+ try {
+ return new String(bytes, CharacterSets.MIMENAME_ISO_8859_1);
+ } catch (UnsupportedEncodingException e) {
+ // Impossible to reach here!
+ Log.e(TAG, "ISO_8859_1 must be supported!", e);
+ return "";
+ }
+ }
+
+ /**
+ * Unpack a given String into a byte[].
+ */
+ @UnsupportedAppUsage
+ public static byte[] getBytes(String data) {
+ try {
+ return data.getBytes(CharacterSets.MIMENAME_ISO_8859_1);
+ } catch (UnsupportedEncodingException e) {
+ // Impossible to reach here!
+ Log.e(TAG, "ISO_8859_1 must be supported!", e);
+ return new byte[0];
+ }
+ }
+
+ /**
+ * Remove all objects in the temporary path.
+ */
+ public void release() {
+ Uri uri = Uri.parse(TEMPORARY_DRM_OBJECT_URI);
+ SqliteWrapper.delete(mContext, mContentResolver, uri, null, null);
+ }
+
+ /**
+ * Find all messages to be sent or downloaded before certain time.
+ */
+ @UnsupportedAppUsage
+ public Cursor getPendingMessages(long dueTime) {
+ Uri.Builder uriBuilder = PendingMessages.CONTENT_URI.buildUpon();
+ uriBuilder.appendQueryParameter("protocol", "mms");
+
+ String selection = PendingMessages.ERROR_TYPE + " < ?"
+ + " AND " + PendingMessages.DUE_TIME + " <= ?";
+
+ String[] selectionArgs = new String[] {
+ String.valueOf(MmsSms.ERR_TYPE_GENERIC_PERMANENT),
+ String.valueOf(dueTime)
+ };
+
+ return SqliteWrapper.query(mContext, mContentResolver,
+ uriBuilder.build(), null, selection, selectionArgs,
+ PendingMessages.DUE_TIME);
+ }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/QuotedPrintable.java b/telephony/java/com/google/android/mms/pdu/QuotedPrintable.java
new file mode 100644
index 0000000..9d6535c
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/QuotedPrintable.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import java.io.ByteArrayOutputStream;
+
+public class QuotedPrintable {
+ private static byte ESCAPE_CHAR = '=';
+
+ /**
+ * Decodes an array quoted-printable characters into an array of original bytes.
+ * Escaped characters are converted back to their original representation.
+ *
+ * <p>
+ * This function implements a subset of
+ * quoted-printable encoding specification (rule #1 and rule #2)
+ * as defined in RFC 1521.
+ * </p>
+ *
+ * @param bytes array of quoted-printable characters
+ * @return array of original bytes,
+ * null if quoted-printable decoding is unsuccessful.
+ */
+ @UnsupportedAppUsage
+ public static final byte[] decodeQuotedPrintable(byte[] bytes) {
+ if (bytes == null) {
+ return null;
+ }
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ for (int i = 0; i < bytes.length; i++) {
+ int b = bytes[i];
+ if (b == ESCAPE_CHAR) {
+ try {
+ if('\r' == (char)bytes[i + 1] &&
+ '\n' == (char)bytes[i + 2]) {
+ i += 2;
+ continue;
+ }
+ int u = Character.digit((char) bytes[++i], 16);
+ int l = Character.digit((char) bytes[++i], 16);
+ if (u == -1 || l == -1) {
+ return null;
+ }
+ buffer.write((char) ((u << 4) + l));
+ } catch (ArrayIndexOutOfBoundsException e) {
+ return null;
+ }
+ } else {
+ buffer.write(b);
+ }
+ }
+ return buffer.toByteArray();
+ }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/ReadOrigInd.java b/telephony/java/com/google/android/mms/pdu/ReadOrigInd.java
new file mode 100644
index 0000000..e38c62d
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/ReadOrigInd.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+public class ReadOrigInd extends GenericPdu {
+ /**
+ * Empty constructor.
+ * Since the Pdu corresponding to this class is constructed
+ * by the Proxy-Relay server, this class is only instantiated
+ * by the Pdu Parser.
+ *
+ * @throws InvalidHeaderValueException if error occurs.
+ */
+ public ReadOrigInd() throws InvalidHeaderValueException {
+ super();
+ setMessageType(PduHeaders.MESSAGE_TYPE_READ_ORIG_IND);
+ }
+
+ /**
+ * Constructor with given headers.
+ *
+ * @param headers Headers for this PDU.
+ */
+ @UnsupportedAppUsage
+ ReadOrigInd(PduHeaders headers) {
+ super(headers);
+ }
+
+ /**
+ * Get Date value.
+ *
+ * @return the value
+ */
+ public long getDate() {
+ return mPduHeaders.getLongInteger(PduHeaders.DATE);
+ }
+
+ /**
+ * Set Date value.
+ *
+ * @param value the value
+ */
+ public void setDate(long value) {
+ mPduHeaders.setLongInteger(value, PduHeaders.DATE);
+ }
+
+ /**
+ * Get From value.
+ * From-value = Value-length
+ * (Address-present-token Encoded-string-value | Insert-address-token)
+ *
+ * @return the value
+ */
+ public EncodedStringValue getFrom() {
+ return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
+ }
+
+ /**
+ * Set From value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setFrom(EncodedStringValue value) {
+ mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
+ }
+
+ /**
+ * Get Message-ID value.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public byte[] getMessageId() {
+ return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
+ }
+
+ /**
+ * Set Message-ID value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setMessageId(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
+ }
+
+ /**
+ * Get X-MMS-Read-status value.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public int getReadStatus() {
+ return mPduHeaders.getOctet(PduHeaders.READ_STATUS);
+ }
+
+ /**
+ * Set X-MMS-Read-status value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ public void setReadStatus(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.READ_STATUS);
+ }
+
+ /**
+ * Get To value.
+ *
+ * @return the value
+ */
+ public EncodedStringValue[] getTo() {
+ return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
+ }
+
+ /**
+ * Set To value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setTo(EncodedStringValue[] value) {
+ mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
+ }
+
+ /*
+ * Optional, not supported header fields:
+ *
+ * public byte[] getApplicId() {return null;}
+ * public void setApplicId(byte[] value) {}
+ *
+ * public byte[] getAuxApplicId() {return null;}
+ * public void getAuxApplicId(byte[] value) {}
+ *
+ * public byte[] getReplyApplicId() {return 0x00;}
+ * public void setReplyApplicId(byte[] value) {}
+ */
+}
diff --git a/telephony/java/com/google/android/mms/pdu/ReadRecInd.java b/telephony/java/com/google/android/mms/pdu/ReadRecInd.java
new file mode 100644
index 0000000..9696bc2
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/ReadRecInd.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+public class ReadRecInd extends GenericPdu {
+ /**
+ * Constructor, used when composing a M-ReadRec.ind pdu.
+ *
+ * @param from the from value
+ * @param messageId the message ID value
+ * @param mmsVersion current viersion of mms
+ * @param readStatus the read status value
+ * @param to the to value
+ * @throws InvalidHeaderValueException if parameters are invalid.
+ * NullPointerException if messageId or to is null.
+ */
+ @UnsupportedAppUsage
+ public ReadRecInd(EncodedStringValue from,
+ byte[] messageId,
+ int mmsVersion,
+ int readStatus,
+ EncodedStringValue[] to) throws InvalidHeaderValueException {
+ super();
+ setMessageType(PduHeaders.MESSAGE_TYPE_READ_REC_IND);
+ setFrom(from);
+ setMessageId(messageId);
+ setMmsVersion(mmsVersion);
+ setTo(to);
+ setReadStatus(readStatus);
+ }
+
+ /**
+ * Constructor with given headers.
+ *
+ * @param headers Headers for this PDU.
+ */
+ @UnsupportedAppUsage
+ ReadRecInd(PduHeaders headers) {
+ super(headers);
+ }
+
+ /**
+ * Get Date value.
+ *
+ * @return the value
+ */
+ public long getDate() {
+ return mPduHeaders.getLongInteger(PduHeaders.DATE);
+ }
+
+ /**
+ * Set Date value.
+ *
+ * @param value the value
+ */
+ @UnsupportedAppUsage
+ public void setDate(long value) {
+ mPduHeaders.setLongInteger(value, PduHeaders.DATE);
+ }
+
+ /**
+ * Get Message-ID value.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public byte[] getMessageId() {
+ return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
+ }
+
+ /**
+ * Set Message-ID value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setMessageId(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
+ }
+
+ /**
+ * Get To value.
+ *
+ * @return the value
+ */
+ public EncodedStringValue[] getTo() {
+ return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
+ }
+
+ /**
+ * Set To value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setTo(EncodedStringValue[] value) {
+ mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
+ }
+
+ /**
+ * Get X-MMS-Read-status value.
+ *
+ * @return the value
+ */
+ public int getReadStatus() {
+ return mPduHeaders.getOctet(PduHeaders.READ_STATUS);
+ }
+
+ /**
+ * Set X-MMS-Read-status value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ public void setReadStatus(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.READ_STATUS);
+ }
+
+ /*
+ * Optional, not supported header fields:
+ *
+ * public byte[] getApplicId() {return null;}
+ * public void setApplicId(byte[] value) {}
+ *
+ * public byte[] getAuxApplicId() {return null;}
+ * public void getAuxApplicId(byte[] value) {}
+ *
+ * public byte[] getReplyApplicId() {return 0x00;}
+ * public void setReplyApplicId(byte[] value) {}
+ */
+}
diff --git a/telephony/java/com/google/android/mms/pdu/RetrieveConf.java b/telephony/java/com/google/android/mms/pdu/RetrieveConf.java
new file mode 100644
index 0000000..03755af
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/RetrieveConf.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+/**
+ * M-Retrieve.conf Pdu.
+ */
+public class RetrieveConf extends MultimediaMessagePdu {
+ /**
+ * Empty constructor.
+ * Since the Pdu corresponding to this class is constructed
+ * by the Proxy-Relay server, this class is only instantiated
+ * by the Pdu Parser.
+ *
+ * @throws InvalidHeaderValueException if error occurs.
+ */
+ @UnsupportedAppUsage
+ public RetrieveConf() throws InvalidHeaderValueException {
+ super();
+ setMessageType(PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF);
+ }
+
+ /**
+ * Constructor with given headers.
+ *
+ * @param headers Headers for this PDU.
+ */
+ RetrieveConf(PduHeaders headers) {
+ super(headers);
+ }
+
+ /**
+ * Constructor with given headers and body
+ *
+ * @param headers Headers for this PDU.
+ * @param body Body of this PDu.
+ */
+ @UnsupportedAppUsage
+ RetrieveConf(PduHeaders headers, PduBody body) {
+ super(headers, body);
+ }
+
+ /**
+ * Get CC value.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public EncodedStringValue[] getCc() {
+ return mPduHeaders.getEncodedStringValues(PduHeaders.CC);
+ }
+
+ /**
+ * Add a "CC" value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ @UnsupportedAppUsage
+ public void addCc(EncodedStringValue value) {
+ mPduHeaders.appendEncodedStringValue(value, PduHeaders.CC);
+ }
+
+ /**
+ * Get Content-type value.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public byte[] getContentType() {
+ return mPduHeaders.getTextString(PduHeaders.CONTENT_TYPE);
+ }
+
+ /**
+ * Set Content-type value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ @UnsupportedAppUsage
+ public void setContentType(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.CONTENT_TYPE);
+ }
+
+ /**
+ * Get X-Mms-Delivery-Report value.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public int getDeliveryReport() {
+ return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT);
+ }
+
+ /**
+ * Set X-Mms-Delivery-Report value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ @UnsupportedAppUsage
+ public void setDeliveryReport(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT);
+ }
+
+ /**
+ * Get From value.
+ * From-value = Value-length
+ * (Address-present-token Encoded-string-value | Insert-address-token)
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public EncodedStringValue getFrom() {
+ return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
+ }
+
+ /**
+ * Set From value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ @UnsupportedAppUsage
+ public void setFrom(EncodedStringValue value) {
+ mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
+ }
+
+ /**
+ * Get X-Mms-Message-Class value.
+ * Message-class-value = Class-identifier | Token-text
+ * Class-identifier = Personal | Advertisement | Informational | Auto
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public byte[] getMessageClass() {
+ return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS);
+ }
+
+ /**
+ * Set X-Mms-Message-Class value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ @UnsupportedAppUsage
+ public void setMessageClass(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS);
+ }
+
+ /**
+ * Get Message-ID value.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public byte[] getMessageId() {
+ return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
+ }
+
+ /**
+ * Set Message-ID value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ @UnsupportedAppUsage
+ public void setMessageId(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
+ }
+
+ /**
+ * Get X-Mms-Read-Report value.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public int getReadReport() {
+ return mPduHeaders.getOctet(PduHeaders.READ_REPORT);
+ }
+
+ /**
+ * Set X-Mms-Read-Report value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ @UnsupportedAppUsage
+ public void setReadReport(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.READ_REPORT);
+ }
+
+ /**
+ * Get X-Mms-Retrieve-Status value.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public int getRetrieveStatus() {
+ return mPduHeaders.getOctet(PduHeaders.RETRIEVE_STATUS);
+ }
+
+ /**
+ * Set X-Mms-Retrieve-Status value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ @UnsupportedAppUsage
+ public void setRetrieveStatus(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.RETRIEVE_STATUS);
+ }
+
+ /**
+ * Get X-Mms-Retrieve-Text value.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public EncodedStringValue getRetrieveText() {
+ return mPduHeaders.getEncodedStringValue(PduHeaders.RETRIEVE_TEXT);
+ }
+
+ /**
+ * Set X-Mms-Retrieve-Text value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ @UnsupportedAppUsage
+ public void setRetrieveText(EncodedStringValue value) {
+ mPduHeaders.setEncodedStringValue(value, PduHeaders.RETRIEVE_TEXT);
+ }
+
+ /**
+ * Get X-Mms-Transaction-Id.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public byte[] getTransactionId() {
+ return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+ }
+
+ /**
+ * Set X-Mms-Transaction-Id.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ @UnsupportedAppUsage
+ public void setTransactionId(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+ }
+
+ /*
+ * Optional, not supported header fields:
+ *
+ * public byte[] getApplicId() {return null;}
+ * public void setApplicId(byte[] value) {}
+ *
+ * public byte[] getAuxApplicId() {return null;}
+ * public void getAuxApplicId(byte[] value) {}
+ *
+ * public byte getContentClass() {return 0x00;}
+ * public void setApplicId(byte value) {}
+ *
+ * public byte getDrmContent() {return 0x00;}
+ * public void setDrmContent(byte value) {}
+ *
+ * public byte getDistributionIndicator() {return 0x00;}
+ * public void setDistributionIndicator(byte value) {}
+ *
+ * public PreviouslySentByValue getPreviouslySentBy() {return null;}
+ * public void setPreviouslySentBy(PreviouslySentByValue value) {}
+ *
+ * public PreviouslySentDateValue getPreviouslySentDate() {}
+ * public void setPreviouslySentDate(PreviouslySentDateValue value) {}
+ *
+ * public MmFlagsValue getMmFlags() {return null;}
+ * public void setMmFlags(MmFlagsValue value) {}
+ *
+ * public MmStateValue getMmState() {return null;}
+ * public void getMmState(MmStateValue value) {}
+ *
+ * public byte[] getReplaceId() {return 0x00;}
+ * public void setReplaceId(byte[] value) {}
+ *
+ * public byte[] getReplyApplicId() {return 0x00;}
+ * public void setReplyApplicId(byte[] value) {}
+ *
+ * public byte getReplyCharging() {return 0x00;}
+ * public void setReplyCharging(byte value) {}
+ *
+ * public byte getReplyChargingDeadline() {return 0x00;}
+ * public void setReplyChargingDeadline(byte value) {}
+ *
+ * public byte[] getReplyChargingId() {return 0x00;}
+ * public void setReplyChargingId(byte[] value) {}
+ *
+ * public long getReplyChargingSize() {return 0;}
+ * public void setReplyChargingSize(long value) {}
+ */
+}
diff --git a/telephony/java/com/google/android/mms/pdu/SendConf.java b/telephony/java/com/google/android/mms/pdu/SendConf.java
new file mode 100644
index 0000000..b859827
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/SendConf.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+public class SendConf extends GenericPdu {
+ /**
+ * Empty constructor.
+ * Since the Pdu corresponding to this class is constructed
+ * by the Proxy-Relay server, this class is only instantiated
+ * by the Pdu Parser.
+ *
+ * @throws InvalidHeaderValueException if error occurs.
+ */
+ @UnsupportedAppUsage
+ public SendConf() throws InvalidHeaderValueException {
+ super();
+ setMessageType(PduHeaders.MESSAGE_TYPE_SEND_CONF);
+ }
+
+ /**
+ * Constructor with given headers.
+ *
+ * @param headers Headers for this PDU.
+ */
+ @UnsupportedAppUsage
+ SendConf(PduHeaders headers) {
+ super(headers);
+ }
+
+ /**
+ * Get Message-ID value.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public byte[] getMessageId() {
+ return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
+ }
+
+ /**
+ * Set Message-ID value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setMessageId(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
+ }
+
+ /**
+ * Get X-Mms-Response-Status.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public int getResponseStatus() {
+ return mPduHeaders.getOctet(PduHeaders.RESPONSE_STATUS);
+ }
+
+ /**
+ * Set X-Mms-Response-Status.
+ *
+ * @param value the values
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ public void setResponseStatus(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.RESPONSE_STATUS);
+ }
+
+ /**
+ * Get X-Mms-Transaction-Id field value.
+ *
+ * @return the X-Mms-Report-Allowed value
+ */
+ @UnsupportedAppUsage
+ public byte[] getTransactionId() {
+ return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+ }
+
+ /**
+ * Set X-Mms-Transaction-Id field value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setTransactionId(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+ }
+
+ /*
+ * Optional, not supported header fields:
+ *
+ * public byte[] getContentLocation() {return null;}
+ * public void setContentLocation(byte[] value) {}
+ *
+ * public EncodedStringValue getResponseText() {return null;}
+ * public void setResponseText(EncodedStringValue value) {}
+ *
+ * public byte getStoreStatus() {return 0x00;}
+ * public void setStoreStatus(byte value) {}
+ *
+ * public byte[] getStoreStatusText() {return null;}
+ * public void setStoreStatusText(byte[] value) {}
+ */
+}
diff --git a/telephony/java/com/google/android/mms/pdu/SendReq.java b/telephony/java/com/google/android/mms/pdu/SendReq.java
new file mode 100644
index 0000000..c1b7f93
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/SendReq.java
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-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.google.android.mms.pdu;
+
+import android.util.Log;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+public class SendReq extends MultimediaMessagePdu {
+ private static final String TAG = "SendReq";
+
+ @UnsupportedAppUsage
+ public SendReq() {
+ super();
+
+ try {
+ setMessageType(PduHeaders.MESSAGE_TYPE_SEND_REQ);
+ setMmsVersion(PduHeaders.CURRENT_MMS_VERSION);
+ // FIXME: Content-type must be decided according to whether
+ // SMIL part present.
+ setContentType("application/vnd.wap.multipart.related".getBytes());
+ setFrom(new EncodedStringValue(PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR.getBytes()));
+ setTransactionId(generateTransactionId());
+ } catch (InvalidHeaderValueException e) {
+ // Impossible to reach here since all headers we set above are valid.
+ Log.e(TAG, "Unexpected InvalidHeaderValueException.", e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ private byte[] generateTransactionId() {
+ String transactionId = "T" + Long.toHexString(System.currentTimeMillis());
+ return transactionId.getBytes();
+ }
+
+ /**
+ * Constructor, used when composing a M-Send.req pdu.
+ *
+ * @param contentType the content type value
+ * @param from the from value
+ * @param mmsVersion current viersion of mms
+ * @param transactionId the transaction-id value
+ * @throws InvalidHeaderValueException if parameters are invalid.
+ * NullPointerException if contentType, form or transactionId is null.
+ */
+ public SendReq(byte[] contentType,
+ EncodedStringValue from,
+ int mmsVersion,
+ byte[] transactionId) throws InvalidHeaderValueException {
+ super();
+ setMessageType(PduHeaders.MESSAGE_TYPE_SEND_REQ);
+ setContentType(contentType);
+ setFrom(from);
+ setMmsVersion(mmsVersion);
+ setTransactionId(transactionId);
+ }
+
+ /**
+ * Constructor with given headers.
+ *
+ * @param headers Headers for this PDU.
+ */
+ SendReq(PduHeaders headers) {
+ super(headers);
+ }
+
+ /**
+ * Constructor with given headers and body
+ *
+ * @param headers Headers for this PDU.
+ * @param body Body of this PDu.
+ */
+ @UnsupportedAppUsage
+ SendReq(PduHeaders headers, PduBody body) {
+ super(headers, body);
+ }
+
+ /**
+ * Get Bcc value.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public EncodedStringValue[] getBcc() {
+ return mPduHeaders.getEncodedStringValues(PduHeaders.BCC);
+ }
+
+ /**
+ * Add a "BCC" value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ @UnsupportedAppUsage
+ public void addBcc(EncodedStringValue value) {
+ mPduHeaders.appendEncodedStringValue(value, PduHeaders.BCC);
+ }
+
+ /**
+ * Set "BCC" value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ @UnsupportedAppUsage
+ public void setBcc(EncodedStringValue[] value) {
+ mPduHeaders.setEncodedStringValues(value, PduHeaders.BCC);
+ }
+
+ /**
+ * Get CC value.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public EncodedStringValue[] getCc() {
+ return mPduHeaders.getEncodedStringValues(PduHeaders.CC);
+ }
+
+ /**
+ * Add a "CC" value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ @UnsupportedAppUsage
+ public void addCc(EncodedStringValue value) {
+ mPduHeaders.appendEncodedStringValue(value, PduHeaders.CC);
+ }
+
+ /**
+ * Set "CC" value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ @UnsupportedAppUsage
+ public void setCc(EncodedStringValue[] value) {
+ mPduHeaders.setEncodedStringValues(value, PduHeaders.CC);
+ }
+
+ /**
+ * Get Content-type value.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public byte[] getContentType() {
+ return mPduHeaders.getTextString(PduHeaders.CONTENT_TYPE);
+ }
+
+ /**
+ * Set Content-type value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ @UnsupportedAppUsage
+ public void setContentType(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.CONTENT_TYPE);
+ }
+
+ /**
+ * Get X-Mms-Delivery-Report value.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public int getDeliveryReport() {
+ return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT);
+ }
+
+ /**
+ * Set X-Mms-Delivery-Report value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ @UnsupportedAppUsage
+ public void setDeliveryReport(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT);
+ }
+
+ /**
+ * Get X-Mms-Expiry value.
+ *
+ * Expiry-value = Value-length
+ * (Absolute-token Date-value | Relative-token Delta-seconds-value)
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public long getExpiry() {
+ return mPduHeaders.getLongInteger(PduHeaders.EXPIRY);
+ }
+
+ /**
+ * Set X-Mms-Expiry value.
+ *
+ * @param value the value
+ */
+ @UnsupportedAppUsage
+ public void setExpiry(long value) {
+ mPduHeaders.setLongInteger(value, PduHeaders.EXPIRY);
+ }
+
+ /**
+ * Get X-Mms-MessageSize value.
+ *
+ * Expiry-value = size of message
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public long getMessageSize() {
+ return mPduHeaders.getLongInteger(PduHeaders.MESSAGE_SIZE);
+ }
+
+ /**
+ * Set X-Mms-MessageSize value.
+ *
+ * @param value the value
+ */
+ @UnsupportedAppUsage
+ public void setMessageSize(long value) {
+ mPduHeaders.setLongInteger(value, PduHeaders.MESSAGE_SIZE);
+ }
+
+ /**
+ * Get X-Mms-Message-Class value.
+ * Message-class-value = Class-identifier | Token-text
+ * Class-identifier = Personal | Advertisement | Informational | Auto
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public byte[] getMessageClass() {
+ return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS);
+ }
+
+ /**
+ * Set X-Mms-Message-Class value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ @UnsupportedAppUsage
+ public void setMessageClass(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS);
+ }
+
+ /**
+ * Get X-Mms-Read-Report value.
+ *
+ * @return the value
+ */
+ @UnsupportedAppUsage
+ public int getReadReport() {
+ return mPduHeaders.getOctet(PduHeaders.READ_REPORT);
+ }
+
+ /**
+ * Set X-Mms-Read-Report value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ @UnsupportedAppUsage
+ public void setReadReport(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.READ_REPORT);
+ }
+
+ /**
+ * Set "To" value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ @UnsupportedAppUsage
+ public void setTo(EncodedStringValue[] value) {
+ mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
+ }
+
+ /**
+ * Get X-Mms-Transaction-Id field value.
+ *
+ * @return the X-Mms-Report-Allowed value
+ */
+ @UnsupportedAppUsage
+ public byte[] getTransactionId() {
+ return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+ }
+
+ /**
+ * Set X-Mms-Transaction-Id field value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ @UnsupportedAppUsage
+ public void setTransactionId(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+ }
+
+ /*
+ * Optional, not supported header fields:
+ *
+ * public byte getAdaptationAllowed() {return 0};
+ * public void setAdaptationAllowed(btye value) {};
+ *
+ * public byte[] getApplicId() {return null;}
+ * public void setApplicId(byte[] value) {}
+ *
+ * public byte[] getAuxApplicId() {return null;}
+ * public void getAuxApplicId(byte[] value) {}
+ *
+ * public byte getContentClass() {return 0x00;}
+ * public void setApplicId(byte value) {}
+ *
+ * public long getDeliveryTime() {return 0};
+ * public void setDeliveryTime(long value) {};
+ *
+ * public byte getDrmContent() {return 0x00;}
+ * public void setDrmContent(byte value) {}
+ *
+ * public MmFlagsValue getMmFlags() {return null;}
+ * public void setMmFlags(MmFlagsValue value) {}
+ *
+ * public MmStateValue getMmState() {return null;}
+ * public void getMmState(MmStateValue value) {}
+ *
+ * public byte[] getReplyApplicId() {return 0x00;}
+ * public void setReplyApplicId(byte[] value) {}
+ *
+ * public byte getReplyCharging() {return 0x00;}
+ * public void setReplyCharging(byte value) {}
+ *
+ * public byte getReplyChargingDeadline() {return 0x00;}
+ * public void setReplyChargingDeadline(byte value) {}
+ *
+ * public byte[] getReplyChargingId() {return 0x00;}
+ * public void setReplyChargingId(byte[] value) {}
+ *
+ * public long getReplyChargingSize() {return 0;}
+ * public void setReplyChargingSize(long value) {}
+ *
+ * public byte[] getReplyApplicId() {return 0x00;}
+ * public void setReplyApplicId(byte[] value) {}
+ *
+ * public byte getStore() {return 0x00;}
+ * public void setStore(byte value) {}
+ */
+}
diff --git a/telephony/java/com/google/android/mms/pdu/package.html b/telephony/java/com/google/android/mms/pdu/package.html
new file mode 100755
index 0000000..c9f96a6
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/package.html
@@ -0,0 +1,5 @@
+<body>
+
+{@hide}
+
+</body>
diff --git a/telephony/java/com/google/android/mms/util/AbstractCache.java b/telephony/java/com/google/android/mms/util/AbstractCache.java
new file mode 100644
index 0000000..ab5d48a
--- /dev/null
+++ b/telephony/java/com/google/android/mms/util/AbstractCache.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2008 Esmertec AG.
+ * 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.google.android.mms.util;
+
+import android.util.Log;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import java.util.HashMap;
+
+public abstract class AbstractCache<K, V> {
+ private static final String TAG = "AbstractCache";
+ private static final boolean DEBUG = false;
+ private static final boolean LOCAL_LOGV = false;
+
+ private static final int MAX_CACHED_ITEMS = 500;
+
+ private final HashMap<K, CacheEntry<V>> mCacheMap;
+
+ @UnsupportedAppUsage
+ protected AbstractCache() {
+ mCacheMap = new HashMap<K, CacheEntry<V>>();
+ }
+
+ @UnsupportedAppUsage
+ public boolean put(K key, V value) {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "Trying to put " + key + " into cache.");
+ }
+
+ if (mCacheMap.size() >= MAX_CACHED_ITEMS) {
+ // TODO Should remove the oldest or least hit cached entry
+ // and then cache the new one.
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "Failed! size limitation reached.");
+ }
+ return false;
+ }
+
+ if (key != null) {
+ CacheEntry<V> cacheEntry = new CacheEntry<V>();
+ cacheEntry.value = value;
+ mCacheMap.put(key, cacheEntry);
+
+ if (LOCAL_LOGV) {
+ Log.v(TAG, key + " cached, " + mCacheMap.size() + " items total.");
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @UnsupportedAppUsage
+ public V get(K key) {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "Trying to get " + key + " from cache.");
+ }
+
+ if (key != null) {
+ CacheEntry<V> cacheEntry = mCacheMap.get(key);
+ if (cacheEntry != null) {
+ cacheEntry.hit++;
+ if (LOCAL_LOGV) {
+ Log.v(TAG, key + " hit " + cacheEntry.hit + " times.");
+ }
+ return cacheEntry.value;
+ }
+ }
+ return null;
+ }
+
+ @UnsupportedAppUsage
+ public V purge(K key) {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "Trying to purge " + key);
+ }
+
+ CacheEntry<V> v = mCacheMap.remove(key);
+
+ if (LOCAL_LOGV) {
+ Log.v(TAG, mCacheMap.size() + " items cached.");
+ }
+
+ return v != null ? v.value : null;
+ }
+
+ @UnsupportedAppUsage
+ public void purgeAll() {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "Purging cache, " + mCacheMap.size()
+ + " items dropped.");
+ }
+ mCacheMap.clear();
+ }
+
+ public int size() {
+ return mCacheMap.size();
+ }
+
+ private static class CacheEntry<V> {
+ int hit;
+ V value;
+ }
+}
diff --git a/telephony/java/com/google/android/mms/util/DownloadDrmHelper.java b/telephony/java/com/google/android/mms/util/DownloadDrmHelper.java
new file mode 100644
index 0000000..118de46
--- /dev/null
+++ b/telephony/java/com/google/android/mms/util/DownloadDrmHelper.java
@@ -0,0 +1,115 @@
+/*
+ * 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.google.android.mms.util;
+
+import android.content.Context;
+import android.drm.DrmManagerClient;
+import android.util.Log;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+public class DownloadDrmHelper {
+ private static final String TAG = "DownloadDrmHelper";
+
+ /** The MIME type of special DRM files */
+ public static final String MIMETYPE_DRM_MESSAGE = "application/vnd.oma.drm.message";
+
+ /** The extensions of special DRM files */
+ public static final String EXTENSION_DRM_MESSAGE = ".dm";
+
+ public static final String EXTENSION_INTERNAL_FWDL = ".fl";
+
+ /**
+ * Checks if the Media Type is a DRM Media Type
+ *
+ * @param drmManagerClient A DrmManagerClient
+ * @param mimetype Media Type to check
+ * @return True if the Media Type is DRM else false
+ */
+ public static boolean isDrmMimeType(Context context, String mimetype) {
+ boolean result = false;
+ if (context != null) {
+ try {
+ DrmManagerClient drmClient = new DrmManagerClient(context);
+ if (drmClient != null && mimetype != null && mimetype.length() > 0) {
+ result = drmClient.canHandle("", mimetype);
+ }
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG,
+ "DrmManagerClient instance could not be created, context is Illegal.");
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "DrmManagerClient didn't initialize properly.");
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Checks if the Media Type needs to be DRM converted
+ *
+ * @param mimetype Media type of the content
+ * @return True if convert is needed else false
+ */
+ @UnsupportedAppUsage
+ public static boolean isDrmConvertNeeded(String mimetype) {
+ return MIMETYPE_DRM_MESSAGE.equals(mimetype);
+ }
+
+ /**
+ * Modifies the file extension for a DRM Forward Lock file NOTE: This
+ * function shouldn't be called if the file shouldn't be DRM converted
+ */
+ @UnsupportedAppUsage
+ public static String modifyDrmFwLockFileExtension(String filename) {
+ if (filename != null) {
+ int extensionIndex;
+ extensionIndex = filename.lastIndexOf(".");
+ if (extensionIndex != -1) {
+ filename = filename.substring(0, extensionIndex);
+ }
+ filename = filename.concat(EXTENSION_INTERNAL_FWDL);
+ }
+ return filename;
+ }
+
+ /**
+ * Gets the original mime type of DRM protected content.
+ *
+ * @param context The context
+ * @param path Path to the file
+ * @param containingMime The current mime type of the file i.e. the
+ * containing mime type
+ * @return The original mime type of the file if DRM protected else the
+ * currentMime
+ */
+ public static String getOriginalMimeType(Context context, String path, String containingMime) {
+ String result = containingMime;
+ DrmManagerClient drmClient = new DrmManagerClient(context);
+ try {
+ if (drmClient.canHandle(path, null)) {
+ result = drmClient.getOriginalMimeType(path);
+ }
+ } catch (IllegalArgumentException ex) {
+ Log.w(TAG,
+ "Can't get original mime type since path is null or empty string.");
+ } catch (IllegalStateException ex) {
+ Log.w(TAG, "DrmManagerClient didn't initialize properly.");
+ }
+ return result;
+ }
+}
diff --git a/telephony/java/com/google/android/mms/util/DrmConvertSession.java b/telephony/java/com/google/android/mms/util/DrmConvertSession.java
new file mode 100644
index 0000000..0e8ec91
--- /dev/null
+++ b/telephony/java/com/google/android/mms/util/DrmConvertSession.java
@@ -0,0 +1,177 @@
+/*
+ * 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.google.android.mms.util;
+
+import android.content.Context;
+import android.drm.DrmConvertedStatus;
+import android.drm.DrmManagerClient;
+import android.provider.Downloads;
+import android.util.Log;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+
+public class DrmConvertSession {
+ private DrmManagerClient mDrmClient;
+ private int mConvertSessionId;
+ private static final String TAG = "DrmConvertSession";
+
+ private DrmConvertSession(DrmManagerClient drmClient, int convertSessionId) {
+ mDrmClient = drmClient;
+ mConvertSessionId = convertSessionId;
+ }
+
+ /**
+ * Start of converting a file.
+ *
+ * @param context The context of the application running the convert session.
+ * @param mimeType Mimetype of content that shall be converted.
+ * @return A convert session or null in case an error occurs.
+ */
+ @UnsupportedAppUsage
+ public static DrmConvertSession open(Context context, String mimeType) {
+ DrmManagerClient drmClient = null;
+ int convertSessionId = -1;
+ if (context != null && mimeType != null && !mimeType.equals("")) {
+ try {
+ drmClient = new DrmManagerClient(context);
+ try {
+ convertSessionId = drmClient.openConvertSession(mimeType);
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "Conversion of Mimetype: " + mimeType
+ + " is not supported.", e);
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Could not access Open DrmFramework.", e);
+ }
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG,
+ "DrmManagerClient instance could not be created, context is Illegal.");
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "DrmManagerClient didn't initialize properly.");
+ }
+ }
+
+ if (drmClient == null || convertSessionId < 0) {
+ return null;
+ } else {
+ return new DrmConvertSession(drmClient, convertSessionId);
+ }
+ }
+ /**
+ * Convert a buffer of data to protected format.
+ *
+ * @param buffer Buffer filled with data to convert.
+ * @param size The number of bytes that shall be converted.
+ * @return A Buffer filled with converted data, if execution is ok, in all
+ * other case null.
+ */
+ @UnsupportedAppUsage
+ public byte [] convert(byte[] inBuffer, int size) {
+ byte[] result = null;
+ if (inBuffer != null) {
+ DrmConvertedStatus convertedStatus = null;
+ try {
+ if (size != inBuffer.length) {
+ byte[] buf = new byte[size];
+ System.arraycopy(inBuffer, 0, buf, 0, size);
+ convertedStatus = mDrmClient.convertData(mConvertSessionId, buf);
+ } else {
+ convertedStatus = mDrmClient.convertData(mConvertSessionId, inBuffer);
+ }
+
+ if (convertedStatus != null &&
+ convertedStatus.statusCode == DrmConvertedStatus.STATUS_OK &&
+ convertedStatus.convertedData != null) {
+ result = convertedStatus.convertedData;
+ }
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "Buffer with data to convert is illegal. Convertsession: "
+ + mConvertSessionId, e);
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Could not convert data. Convertsession: " +
+ mConvertSessionId, e);
+ }
+ } else {
+ throw new IllegalArgumentException("Parameter inBuffer is null");
+ }
+ return result;
+ }
+
+ /**
+ * Ends a conversion session of a file.
+ *
+ * @param fileName The filename of the converted file.
+ * @return Downloads.Impl.STATUS_SUCCESS if execution is ok.
+ * Downloads.Impl.STATUS_FILE_ERROR in case converted file can not
+ * be accessed. Downloads.Impl.STATUS_NOT_ACCEPTABLE if a problem
+ * occurs when accessing drm framework.
+ * Downloads.Impl.STATUS_UNKNOWN_ERROR if a general error occurred.
+ */
+ @UnsupportedAppUsage
+ public int close(String filename) {
+ DrmConvertedStatus convertedStatus = null;
+ int result = Downloads.Impl.STATUS_UNKNOWN_ERROR;
+ if (mDrmClient != null && mConvertSessionId >= 0) {
+ try {
+ convertedStatus = mDrmClient.closeConvertSession(mConvertSessionId);
+ if (convertedStatus == null ||
+ convertedStatus.statusCode != DrmConvertedStatus.STATUS_OK ||
+ convertedStatus.convertedData == null) {
+ result = Downloads.Impl.STATUS_NOT_ACCEPTABLE;
+ } else {
+ RandomAccessFile rndAccessFile = null;
+ try {
+ rndAccessFile = new RandomAccessFile(filename, "rw");
+ rndAccessFile.seek(convertedStatus.offset);
+ rndAccessFile.write(convertedStatus.convertedData);
+ result = Downloads.Impl.STATUS_SUCCESS;
+ } catch (FileNotFoundException e) {
+ result = Downloads.Impl.STATUS_FILE_ERROR;
+ Log.w(TAG, "File: " + filename + " could not be found.", e);
+ } catch (IOException e) {
+ result = Downloads.Impl.STATUS_FILE_ERROR;
+ Log.w(TAG, "Could not access File: " + filename + " .", e);
+ } catch (IllegalArgumentException e) {
+ result = Downloads.Impl.STATUS_FILE_ERROR;
+ Log.w(TAG, "Could not open file in mode: rw", e);
+ } catch (SecurityException e) {
+ Log.w(TAG, "Access to File: " + filename +
+ " was denied denied by SecurityManager.", e);
+ } finally {
+ if (rndAccessFile != null) {
+ try {
+ rndAccessFile.close();
+ } catch (IOException e) {
+ result = Downloads.Impl.STATUS_FILE_ERROR;
+ Log.w(TAG, "Failed to close File:" + filename
+ + ".", e);
+ }
+ }
+ }
+ }
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Could not close convertsession. Convertsession: " +
+ mConvertSessionId, e);
+ }
+ }
+ return result;
+ }
+}
diff --git a/telephony/java/com/google/android/mms/util/PduCache.java b/telephony/java/com/google/android/mms/util/PduCache.java
new file mode 100644
index 0000000..94e3894
--- /dev/null
+++ b/telephony/java/com/google/android/mms/util/PduCache.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2008 Esmertec AG.
+ * 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.google.android.mms.util;
+
+import android.content.ContentUris;
+import android.content.UriMatcher;
+import android.net.Uri;
+import android.provider.Telephony.Mms;
+import android.util.Log;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import java.util.HashMap;
+import java.util.HashSet;
+
+public final class PduCache extends AbstractCache<Uri, PduCacheEntry> {
+ private static final String TAG = "PduCache";
+ private static final boolean DEBUG = false;
+ private static final boolean LOCAL_LOGV = false;
+
+ private static final int MMS_ALL = 0;
+ private static final int MMS_ALL_ID = 1;
+ private static final int MMS_INBOX = 2;
+ private static final int MMS_INBOX_ID = 3;
+ private static final int MMS_SENT = 4;
+ private static final int MMS_SENT_ID = 5;
+ private static final int MMS_DRAFTS = 6;
+ private static final int MMS_DRAFTS_ID = 7;
+ private static final int MMS_OUTBOX = 8;
+ private static final int MMS_OUTBOX_ID = 9;
+ private static final int MMS_CONVERSATION = 10;
+ private static final int MMS_CONVERSATION_ID = 11;
+
+ private static final UriMatcher URI_MATCHER;
+ private static final HashMap<Integer, Integer> MATCH_TO_MSGBOX_ID_MAP;
+
+ private static PduCache sInstance;
+
+ static {
+ URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
+ URI_MATCHER.addURI("mms", null, MMS_ALL);
+ URI_MATCHER.addURI("mms", "#", MMS_ALL_ID);
+ URI_MATCHER.addURI("mms", "inbox", MMS_INBOX);
+ URI_MATCHER.addURI("mms", "inbox/#", MMS_INBOX_ID);
+ URI_MATCHER.addURI("mms", "sent", MMS_SENT);
+ URI_MATCHER.addURI("mms", "sent/#", MMS_SENT_ID);
+ URI_MATCHER.addURI("mms", "drafts", MMS_DRAFTS);
+ URI_MATCHER.addURI("mms", "drafts/#", MMS_DRAFTS_ID);
+ URI_MATCHER.addURI("mms", "outbox", MMS_OUTBOX);
+ URI_MATCHER.addURI("mms", "outbox/#", MMS_OUTBOX_ID);
+ URI_MATCHER.addURI("mms-sms", "conversations", MMS_CONVERSATION);
+ URI_MATCHER.addURI("mms-sms", "conversations/#", MMS_CONVERSATION_ID);
+
+ MATCH_TO_MSGBOX_ID_MAP = new HashMap<Integer, Integer>();
+ MATCH_TO_MSGBOX_ID_MAP.put(MMS_INBOX, Mms.MESSAGE_BOX_INBOX);
+ MATCH_TO_MSGBOX_ID_MAP.put(MMS_SENT, Mms.MESSAGE_BOX_SENT);
+ MATCH_TO_MSGBOX_ID_MAP.put(MMS_DRAFTS, Mms.MESSAGE_BOX_DRAFTS);
+ MATCH_TO_MSGBOX_ID_MAP.put(MMS_OUTBOX, Mms.MESSAGE_BOX_OUTBOX);
+ }
+
+ private final HashMap<Integer, HashSet<Uri>> mMessageBoxes;
+ private final HashMap<Long, HashSet<Uri>> mThreads;
+ private final HashSet<Uri> mUpdating;
+
+ @UnsupportedAppUsage
+ private PduCache() {
+ mMessageBoxes = new HashMap<Integer, HashSet<Uri>>();
+ mThreads = new HashMap<Long, HashSet<Uri>>();
+ mUpdating = new HashSet<Uri>();
+ }
+
+ @UnsupportedAppUsage
+ synchronized public static final PduCache getInstance() {
+ if (sInstance == null) {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "Constructing new PduCache instance.");
+ }
+ sInstance = new PduCache();
+ }
+ return sInstance;
+ }
+
+ @Override
+ synchronized public boolean put(Uri uri, PduCacheEntry entry) {
+ int msgBoxId = entry.getMessageBox();
+ HashSet<Uri> msgBox = mMessageBoxes.get(msgBoxId);
+ if (msgBox == null) {
+ msgBox = new HashSet<Uri>();
+ mMessageBoxes.put(msgBoxId, msgBox);
+ }
+
+ long threadId = entry.getThreadId();
+ HashSet<Uri> thread = mThreads.get(threadId);
+ if (thread == null) {
+ thread = new HashSet<Uri>();
+ mThreads.put(threadId, thread);
+ }
+
+ Uri finalKey = normalizeKey(uri);
+ boolean result = super.put(finalKey, entry);
+ if (result) {
+ msgBox.add(finalKey);
+ thread.add(finalKey);
+ }
+ setUpdating(uri, false);
+ return result;
+ }
+
+ synchronized public void setUpdating(Uri uri, boolean updating) {
+ if (updating) {
+ mUpdating.add(uri);
+ } else {
+ mUpdating.remove(uri);
+ }
+ }
+
+ @UnsupportedAppUsage
+ synchronized public boolean isUpdating(Uri uri) {
+ return mUpdating.contains(uri);
+ }
+
+ @Override
+ @UnsupportedAppUsage
+ synchronized public PduCacheEntry purge(Uri uri) {
+ int match = URI_MATCHER.match(uri);
+ switch (match) {
+ case MMS_ALL_ID:
+ return purgeSingleEntry(uri);
+ case MMS_INBOX_ID:
+ case MMS_SENT_ID:
+ case MMS_DRAFTS_ID:
+ case MMS_OUTBOX_ID:
+ String msgId = uri.getLastPathSegment();
+ return purgeSingleEntry(Uri.withAppendedPath(Mms.CONTENT_URI, msgId));
+ // Implicit batch of purge, return null.
+ case MMS_ALL:
+ case MMS_CONVERSATION:
+ purgeAll();
+ return null;
+ case MMS_INBOX:
+ case MMS_SENT:
+ case MMS_DRAFTS:
+ case MMS_OUTBOX:
+ purgeByMessageBox(MATCH_TO_MSGBOX_ID_MAP.get(match));
+ return null;
+ case MMS_CONVERSATION_ID:
+ purgeByThreadId(ContentUris.parseId(uri));
+ return null;
+ default:
+ return null;
+ }
+ }
+
+ private PduCacheEntry purgeSingleEntry(Uri key) {
+ mUpdating.remove(key);
+ PduCacheEntry entry = super.purge(key);
+ if (entry != null) {
+ removeFromThreads(key, entry);
+ removeFromMessageBoxes(key, entry);
+ return entry;
+ }
+ return null;
+ }
+
+ @UnsupportedAppUsage
+ @Override
+ synchronized public void purgeAll() {
+ super.purgeAll();
+
+ mMessageBoxes.clear();
+ mThreads.clear();
+ mUpdating.clear();
+ }
+
+ /**
+ * @param uri The Uri to be normalized.
+ * @return Uri The normalized key of cached entry.
+ */
+ private Uri normalizeKey(Uri uri) {
+ int match = URI_MATCHER.match(uri);
+ Uri normalizedKey = null;
+
+ switch (match) {
+ case MMS_ALL_ID:
+ normalizedKey = uri;
+ break;
+ case MMS_INBOX_ID:
+ case MMS_SENT_ID:
+ case MMS_DRAFTS_ID:
+ case MMS_OUTBOX_ID:
+ String msgId = uri.getLastPathSegment();
+ normalizedKey = Uri.withAppendedPath(Mms.CONTENT_URI, msgId);
+ break;
+ default:
+ return null;
+ }
+
+ if (LOCAL_LOGV) {
+ Log.v(TAG, uri + " -> " + normalizedKey);
+ }
+ return normalizedKey;
+ }
+
+ private void purgeByMessageBox(Integer msgBoxId) {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "Purge cache in message box: " + msgBoxId);
+ }
+
+ if (msgBoxId != null) {
+ HashSet<Uri> msgBox = mMessageBoxes.remove(msgBoxId);
+ if (msgBox != null) {
+ for (Uri key : msgBox) {
+ mUpdating.remove(key);
+ PduCacheEntry entry = super.purge(key);
+ if (entry != null) {
+ removeFromThreads(key, entry);
+ }
+ }
+ }
+ }
+ }
+
+ private void removeFromThreads(Uri key, PduCacheEntry entry) {
+ HashSet<Uri> thread = mThreads.get(entry.getThreadId());
+ if (thread != null) {
+ thread.remove(key);
+ }
+ }
+
+ private void purgeByThreadId(long threadId) {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "Purge cache in thread: " + threadId);
+ }
+
+ HashSet<Uri> thread = mThreads.remove(threadId);
+ if (thread != null) {
+ for (Uri key : thread) {
+ mUpdating.remove(key);
+ PduCacheEntry entry = super.purge(key);
+ if (entry != null) {
+ removeFromMessageBoxes(key, entry);
+ }
+ }
+ }
+ }
+
+ private void removeFromMessageBoxes(Uri key, PduCacheEntry entry) {
+ HashSet<Uri> msgBox = mThreads.get(Long.valueOf(entry.getMessageBox()));
+ if (msgBox != null) {
+ msgBox.remove(key);
+ }
+ }
+}
diff --git a/telephony/java/com/google/android/mms/util/PduCacheEntry.java b/telephony/java/com/google/android/mms/util/PduCacheEntry.java
new file mode 100644
index 0000000..1ecd1bf
--- /dev/null
+++ b/telephony/java/com/google/android/mms/util/PduCacheEntry.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2008 Esmertec AG.
+ * 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.google.android.mms.util;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.pdu.GenericPdu;
+
+public final class PduCacheEntry {
+ private final GenericPdu mPdu;
+ private final int mMessageBox;
+ private final long mThreadId;
+
+ @UnsupportedAppUsage
+ public PduCacheEntry(GenericPdu pdu, int msgBox, long threadId) {
+ mPdu = pdu;
+ mMessageBox = msgBox;
+ mThreadId = threadId;
+ }
+
+ @UnsupportedAppUsage
+ public GenericPdu getPdu() {
+ return mPdu;
+ }
+
+ @UnsupportedAppUsage
+ public int getMessageBox() {
+ return mMessageBox;
+ }
+
+ @UnsupportedAppUsage
+ public long getThreadId() {
+ return mThreadId;
+ }
+}
diff --git a/telephony/java/com/google/android/mms/util/SqliteWrapper.java b/telephony/java/com/google/android/mms/util/SqliteWrapper.java
new file mode 100644
index 0000000..2dd1dc1
--- /dev/null
+++ b/telephony/java/com/google/android/mms/util/SqliteWrapper.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2008 Esmertec AG.
+ * 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.google.android.mms.util;
+
+import android.app.ActivityManager;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.net.Uri;
+import android.util.Log;
+import android.widget.Toast;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+public final class SqliteWrapper {
+ private static final String TAG = "SqliteWrapper";
+ private static final String SQLITE_EXCEPTION_DETAIL_MESSAGE
+ = "unable to open database file";
+
+ private SqliteWrapper() {
+ // Forbidden being instantiated.
+ }
+
+ // FIXME: It looks like outInfo.lowMemory does not work well as we expected.
+ // after run command: adb shell fillup -p 100, outInfo.lowMemory is still false.
+ private static boolean isLowMemory(Context context) {
+ if (null == context) {
+ return false;
+ }
+
+ ActivityManager am = (ActivityManager)
+ context.getSystemService(Context.ACTIVITY_SERVICE);
+ ActivityManager.MemoryInfo outInfo = new ActivityManager.MemoryInfo();
+ am.getMemoryInfo(outInfo);
+
+ return outInfo.lowMemory;
+ }
+
+ // FIXME: need to optimize this method.
+ private static boolean isLowMemory(SQLiteException e) {
+ return e.getMessage().equals(SQLITE_EXCEPTION_DETAIL_MESSAGE);
+ }
+
+ @UnsupportedAppUsage
+ public static void checkSQLiteException(Context context, SQLiteException e) {
+ if (isLowMemory(e)) {
+ Toast.makeText(context, com.android.internal.R.string.low_memory,
+ Toast.LENGTH_SHORT).show();
+ } else {
+ throw e;
+ }
+ }
+
+ @UnsupportedAppUsage
+ public static Cursor query(Context context, ContentResolver resolver, Uri uri,
+ String[] projection, String selection, String[] selectionArgs, String sortOrder) {
+ try {
+ return resolver.query(uri, projection, selection, selectionArgs, sortOrder);
+ } catch (SQLiteException e) {
+ Log.e(TAG, "Catch a SQLiteException when query: ", e);
+ checkSQLiteException(context, e);
+ return null;
+ }
+ }
+
+ @UnsupportedAppUsage
+ public static boolean requery(Context context, Cursor cursor) {
+ try {
+ return cursor.requery();
+ } catch (SQLiteException e) {
+ Log.e(TAG, "Catch a SQLiteException when requery: ", e);
+ checkSQLiteException(context, e);
+ return false;
+ }
+ }
+ @UnsupportedAppUsage
+ public static int update(Context context, ContentResolver resolver, Uri uri,
+ ContentValues values, String where, String[] selectionArgs) {
+ try {
+ return resolver.update(uri, values, where, selectionArgs);
+ } catch (SQLiteException e) {
+ Log.e(TAG, "Catch a SQLiteException when update: ", e);
+ checkSQLiteException(context, e);
+ return -1;
+ }
+ }
+
+ @UnsupportedAppUsage
+ public static int delete(Context context, ContentResolver resolver, Uri uri,
+ String where, String[] selectionArgs) {
+ try {
+ return resolver.delete(uri, where, selectionArgs);
+ } catch (SQLiteException e) {
+ Log.e(TAG, "Catch a SQLiteException when delete: ", e);
+ checkSQLiteException(context, e);
+ return -1;
+ }
+ }
+
+ @UnsupportedAppUsage
+ public static Uri insert(Context context, ContentResolver resolver,
+ Uri uri, ContentValues values) {
+ try {
+ return resolver.insert(uri, values);
+ } catch (SQLiteException e) {
+ Log.e(TAG, "Catch a SQLiteException when insert: ", e);
+ checkSQLiteException(context, e);
+ return null;
+ }
+ }
+}
diff --git a/telephony/java/com/google/android/mms/util/package.html b/telephony/java/com/google/android/mms/util/package.html
new file mode 100755
index 0000000..c9f96a6
--- /dev/null
+++ b/telephony/java/com/google/android/mms/util/package.html
@@ -0,0 +1,5 @@
+<body>
+
+{@hide}
+
+</body>
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index ff4c970..79f5095 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -765,6 +765,45 @@
assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_B);
}
+ /**
+ * Test default monitoring duration is used when PackageWatchdog#startObservingHealth is offered
+ * an invalid durationMs.
+ */
+ @Test
+ public void testInvalidMonitoringDuration_beforeExpiry() {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver observer = new TestObserver(OBSERVER_NAME_1);
+
+ watchdog.startObservingHealth(observer, Arrays.asList(APP_A), -1);
+ // Note: Don't move too close to the expiration time otherwise the handler will be thrashed
+ // by PackageWatchdog#scheduleNextSyncStateLocked which keeps posting runnables with very
+ // small timeouts.
+ moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_OBSERVING_DURATION_MS - 100);
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+
+ // We should receive APP_A since the observer hasn't expired
+ assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A);
+ }
+
+ /**
+ * Test default monitoring duration is used when PackageWatchdog#startObservingHealth is offered
+ * an invalid durationMs.
+ */
+ @Test
+ public void testInvalidMonitoringDuration_afterExpiry() {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver observer = new TestObserver(OBSERVER_NAME_1);
+
+ watchdog.startObservingHealth(observer, Arrays.asList(APP_A), -1);
+ moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_OBSERVING_DURATION_MS + 1);
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+
+ // We should receive nothing since the observer has expired
+ assertThat(observer.mHealthCheckFailedPackages).isEmpty();
+ }
+
/** Test we are notified when enough failures are triggered within any window. */
@Test
public void testFailureTriggerWindow() {
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 7ffa5ff..137fbd6 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -246,6 +246,36 @@
Printer* printer_;
};
+std::string OverlayablePoliciesToString(OverlayableItem::PolicyFlags policies) {
+ static const std::map<OverlayableItem::PolicyFlags, std::string> kFlagToString = {
+ {OverlayableItem::kPublic, "public"},
+ {OverlayableItem::kSystem, "system"},
+ {OverlayableItem::kVendor, "vendor"},
+ {OverlayableItem::kProduct, "product"},
+ {OverlayableItem::kSignature, "signature"},
+ {OverlayableItem::kOdm, "odm"},
+ {OverlayableItem::kOem, "oem"},
+ };
+ std::string str;
+ for (auto const& policy : kFlagToString) {
+ if ((policies & policy.first) != policy.first) {
+ continue;
+ }
+ if (!str.empty()) {
+ str.append("|");
+ }
+ str.append(policy.second);
+ policies &= ~policy.first;
+ }
+ if (policies != 0) {
+ if (!str.empty()) {
+ str.append("|");
+ }
+ str.append(StringPrintf("0x%08x", policies));
+ }
+ return !str.empty() ? str : "none";
+}
+
} // namespace
void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions& options,
@@ -312,6 +342,10 @@
break;
}
+ if (entry->overlayable_item) {
+ printer->Print(" OVERLAYABLE");
+ }
+
printer->Println();
if (options.show_values) {
@@ -525,4 +559,62 @@
doc.root->Accept(&xml_visitor);
}
+struct DumpOverlayableEntry {
+ std::string overlayable_section;
+ std::string policy_subsection;
+ std::string resource_name;
+};
+
+void Debug::DumpOverlayable(const ResourceTable& table, text::Printer* printer) {
+ std::vector<DumpOverlayableEntry> items;
+ for (const auto& package : table.packages) {
+ for (const auto& type : package->types) {
+ for (const auto& entry : type->entries) {
+ if (entry->overlayable_item) {
+ const auto& overlayable_item = entry->overlayable_item.value();
+ const auto overlayable_section = StringPrintf(R"(name="%s" actor="%s")",
+ overlayable_item.overlayable->name.c_str(),
+ overlayable_item.overlayable->actor.c_str());
+ const auto policy_subsection = StringPrintf(R"(policies="%s")",
+ OverlayablePoliciesToString(overlayable_item.policies).c_str());
+ const auto value =
+ StringPrintf("%s/%s", to_string(type->type).data(), entry->name.c_str());
+ items.push_back(DumpOverlayableEntry{overlayable_section, policy_subsection, value});
+ }
+ }
+ }
+ }
+
+ std::sort(items.begin(), items.end(),
+ [](const DumpOverlayableEntry& a, const DumpOverlayableEntry& b) {
+ if (a.overlayable_section != b.overlayable_section) {
+ return a.overlayable_section < b.overlayable_section;
+ }
+ if (a.policy_subsection != b.policy_subsection) {
+ return a.policy_subsection < b.policy_subsection;
+ }
+ return a.resource_name < b.resource_name;
+ });
+
+ std::string last_overlayable_section;
+ std::string last_policy_subsection;
+ for (const auto& item : items) {
+ if (last_overlayable_section != item.overlayable_section) {
+ printer->Println(item.overlayable_section);
+ last_overlayable_section = item.overlayable_section;
+ }
+ if (last_policy_subsection != item.policy_subsection) {
+ printer->Indent();
+ printer->Println(item.policy_subsection);
+ last_policy_subsection = item.policy_subsection;
+ printer->Undent();
+ }
+ printer->Indent();
+ printer->Indent();
+ printer->Println(item.resource_name);
+ printer->Undent();
+ printer->Undent();
+ }
+}
+
} // namespace aapt
diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h
index a43197c..9443d60 100644
--- a/tools/aapt2/Debug.h
+++ b/tools/aapt2/Debug.h
@@ -39,6 +39,7 @@
static void DumpHex(const void* data, size_t len);
static void DumpXml(const xml::XmlResource& doc, text::Printer* printer);
static void DumpResStringPool(const android::ResStringPool* pool, text::Printer* printer);
+ static void DumpOverlayable(const ResourceTable& table, text::Printer* printer);
};
} // namespace aapt
diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp
index 429aff1..3982d12 100644
--- a/tools/aapt2/cmd/Dump.cpp
+++ b/tools/aapt2/cmd/Dump.cpp
@@ -394,6 +394,17 @@
return 0;
}
+int DumpOverlayableCommand::Dump(LoadedApk* apk) {
+ ResourceTable* table = apk->GetResourceTable();
+ if (!table) {
+ GetDiagnostics()->Error(DiagMessage() << "Failed to retrieve resource table");
+ return 1;
+ }
+
+ Debug::DumpOverlayable(*table, GetPrinter());
+ return 0;
+}
+
const char DumpBadgerCommand::kBadgerData[2925] = {
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 32, 95, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
diff --git a/tools/aapt2/cmd/Dump.h b/tools/aapt2/cmd/Dump.h
index 7ded9bc..cd51f7a 100644
--- a/tools/aapt2/cmd/Dump.h
+++ b/tools/aapt2/cmd/Dump.h
@@ -240,6 +240,16 @@
std::vector<std::string> files_;
};
+class DumpOverlayableCommand : public DumpApkCommand {
+ public:
+ explicit DumpOverlayableCommand(text::Printer* printer, IDiagnostics* diag)
+ : DumpApkCommand("overlayable", printer, diag) {
+ SetDescription("Print the <overlayable> resources of an APK.");
+ }
+
+ int Dump(LoadedApk* apk) override;
+};
+
/** The default dump command. Performs no action because a subcommand is required. */
class DumpCommand : public Command {
public:
@@ -255,8 +265,8 @@
AddOptionalSubcommand(util::make_unique<DumpTableCommand>(printer, diag_));
AddOptionalSubcommand(util::make_unique<DumpXmlStringsCommand>(printer, diag_));
AddOptionalSubcommand(util::make_unique<DumpXmlTreeCommand>(printer, diag_));
+ AddOptionalSubcommand(util::make_unique<DumpOverlayableCommand>(printer, diag_));
AddOptionalSubcommand(util::make_unique<DumpBadgerCommand>(printer), /* hidden */ true);
- // TODO(b/120609160): Add aapt2 overlayable dump command
}
int Action(const std::vector<std::string>& args) override {
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index 5e06818..e36668e 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -53,9 +53,9 @@
using ::android::ResTable_config;
using ::android::StringPiece;
using ::android::base::ReadFileToString;
-using ::android::base::WriteStringToFile;
using ::android::base::StringAppendF;
using ::android::base::StringPrintf;
+using ::android::base::WriteStringToFile;
namespace aapt {
@@ -300,29 +300,7 @@
OptimizeContext* context_;
};
-bool ExtractObfuscationWhitelistFromConfig(const std::string& path, OptimizeContext* context,
- OptimizeOptions* options) {
- std::string contents;
- if (!ReadFileToString(path, &contents, true)) {
- context->GetDiagnostics()->Error(DiagMessage()
- << "failed to parse whitelist from config file: " << path);
- return false;
- }
- for (StringPiece resource_name : util::Tokenize(contents, ',')) {
- options->table_flattener_options.whitelisted_resources.insert(
- resource_name.to_string());
- }
- return true;
-}
-
-bool ExtractConfig(const std::string& path, OptimizeContext* context,
- OptimizeOptions* options) {
- std::string content;
- if (!android::base::ReadFileToString(path, &content, true /*follow_symlinks*/)) {
- context->GetDiagnostics()->Error(DiagMessage(path) << "failed reading whitelist");
- return false;
- }
-
+bool ParseConfig(const std::string& content, IAaptContext* context, OptimizeOptions* options) {
size_t line_no = 0;
for (StringPiece line : util::Tokenize(content, '\n')) {
line_no++;
@@ -351,15 +329,24 @@
for (StringPiece directive : util::Tokenize(directives, ',')) {
if (directive == "remove") {
options->resources_blacklist.insert(resource_name.ToResourceName());
- } else if (directive == "no_obfuscate") {
- options->table_flattener_options.whitelisted_resources.insert(
- resource_name.entry.to_string());
+ } else if (directive == "no_collapse" || directive == "no_obfuscate") {
+ options->table_flattener_options.name_collapse_exemptions.insert(
+ resource_name.ToResourceName());
}
}
}
return true;
}
+bool ExtractConfig(const std::string& path, IAaptContext* context, OptimizeOptions* options) {
+ std::string content;
+ if (!android::base::ReadFileToString(path, &content, true /*follow_symlinks*/)) {
+ context->GetDiagnostics()->Error(DiagMessage(path) << "failed reading config file");
+ return false;
+ }
+ return ParseConfig(content, context, options);
+}
+
bool ExtractAppDataFromManifest(OptimizeContext* context, const LoadedApk* apk,
OptimizeOptions* out_options) {
const xml::XmlResource* manifest = apk->GetManifest();
@@ -467,15 +454,6 @@
}
}
- if (options_.table_flattener_options.collapse_key_stringpool) {
- if (whitelist_path_) {
- std::string& path = whitelist_path_.value();
- if (!ExtractObfuscationWhitelistFromConfig(path, &context, &options_)) {
- return 1;
- }
- }
- }
-
if (resources_config_path_) {
std::string& path = resources_config_path_.value();
if (!ExtractConfig(path, &context, &options_)) {
diff --git a/tools/aapt2/cmd/Optimize.h b/tools/aapt2/cmd/Optimize.h
index ce3b2ad..5070ccc 100644
--- a/tools/aapt2/cmd/Optimize.h
+++ b/tools/aapt2/cmd/Optimize.h
@@ -78,10 +78,6 @@
"All the resources that would be unused on devices of the given densities will be \n"
"removed from the APK.",
&target_densities_);
- AddOptionalFlag("--whitelist-path",
- "Path to the whitelist.cfg file containing whitelisted resources \n"
- "whose names should not be altered in final resource tables.",
- &whitelist_path_);
AddOptionalFlag("--resources-config-path",
"Path to the resources.cfg file containing the list of resources and \n"
"directives to each resource. \n"
@@ -127,7 +123,6 @@
const std::string &file_path);
Maybe<std::string> config_path_;
- Maybe<std::string> whitelist_path_;
Maybe<std::string> resources_config_path_;
Maybe<std::string> target_densities_;
std::vector<std::string> configs_;
diff --git a/tools/aapt2/cmd/Optimize_test.cpp b/tools/aapt2/cmd/Optimize_test.cpp
new file mode 100644
index 0000000..ac681e8
--- /dev/null
+++ b/tools/aapt2/cmd/Optimize_test.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2019 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 "Optimize.h"
+
+#include "AppInfo.h"
+#include "Diagnostics.h"
+#include "LoadedApk.h"
+#include "Resource.h"
+#include "test/Test.h"
+
+using testing::Contains;
+using testing::Eq;
+
+namespace aapt {
+
+bool ParseConfig(const std::string&, IAaptContext*, OptimizeOptions*);
+
+using OptimizeTest = CommandTestFixture;
+
+TEST_F(OptimizeTest, ParseConfigWithNoCollapseExemptions) {
+ const std::string& content = R"(
+string/foo#no_collapse
+dimen/bar#no_collapse
+)";
+ aapt::test::Context context;
+ OptimizeOptions options;
+ ParseConfig(content, &context, &options);
+
+ const std::set<ResourceName>& name_collapse_exemptions =
+ options.table_flattener_options.name_collapse_exemptions;
+
+ ASSERT_THAT(name_collapse_exemptions.size(), Eq(2));
+ EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kString, "foo")));
+ EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kDimen, "bar")));
+}
+
+TEST_F(OptimizeTest, ParseConfigWithNoObfuscateExemptions) {
+ const std::string& content = R"(
+string/foo#no_obfuscate
+dimen/bar#no_obfuscate
+)";
+ aapt::test::Context context;
+ OptimizeOptions options;
+ ParseConfig(content, &context, &options);
+
+ const std::set<ResourceName>& name_collapse_exemptions =
+ options.table_flattener_options.name_collapse_exemptions;
+
+ ASSERT_THAT(name_collapse_exemptions.size(), Eq(2));
+ EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kString, "foo")));
+ EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kDimen, "bar")));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index b932117..58e232c 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -228,14 +228,15 @@
public:
PackageFlattener(IAaptContext* context, ResourceTablePackage* package,
const std::map<size_t, std::string>* shared_libs, bool use_sparse_entries,
- bool collapse_key_stringpool, const std::set<std::string>& whitelisted_resources)
+ bool collapse_key_stringpool,
+ const std::set<ResourceName>& name_collapse_exemptions)
: context_(context),
diag_(context->GetDiagnostics()),
package_(package),
shared_libs_(shared_libs),
use_sparse_entries_(use_sparse_entries),
collapse_key_stringpool_(collapse_key_stringpool),
- whitelisted_resources_(whitelisted_resources) {
+ name_collapse_exemptions_(name_collapse_exemptions) {
}
bool FlattenPackage(BigBuffer* buffer) {
@@ -652,11 +653,12 @@
for (ResourceEntry* entry : sorted_entries) {
uint32_t local_key_index;
+ ResourceName resource_name({}, type->type, entry->name);
if (!collapse_key_stringpool_ ||
- whitelisted_resources_.find(entry->name) != whitelisted_resources_.end()) {
+ name_collapse_exemptions_.find(resource_name) != name_collapse_exemptions_.end()) {
local_key_index = (uint32_t)key_pool_.MakeRef(entry->name).index();
} else {
- // resource isn't whitelisted, add it as obfuscated value
+ // resource isn't exempt from collapse, add it as obfuscated value
local_key_index = (uint32_t)key_pool_.MakeRef(obfuscated_resource_name).index();
}
// Group values by configuration.
@@ -712,7 +714,7 @@
StringPool type_pool_;
StringPool key_pool_;
bool collapse_key_stringpool_;
- const std::set<std::string>& whitelisted_resources_;
+ const std::set<ResourceName>& name_collapse_exemptions_;
};
} // namespace
@@ -760,7 +762,7 @@
PackageFlattener flattener(context, package.get(), &table->included_packages_,
options_.use_sparse_entries, options_.collapse_key_stringpool,
- options_.whitelisted_resources);
+ options_.name_collapse_exemptions);
if (!flattener.FlattenPackage(&package_buffer)) {
return false;
}
diff --git a/tools/aapt2/format/binary/TableFlattener.h b/tools/aapt2/format/binary/TableFlattener.h
index 73c1729..4360db1 100644
--- a/tools/aapt2/format/binary/TableFlattener.h
+++ b/tools/aapt2/format/binary/TableFlattener.h
@@ -19,6 +19,7 @@
#include "android-base/macros.h"
+#include "Resource.h"
#include "ResourceTable.h"
#include "process/IResourceTableConsumer.h"
#include "util/BigBuffer.h"
@@ -41,8 +42,8 @@
// have name indices that point to this single value
bool collapse_key_stringpool = false;
- // Set of whitelisted resource names to avoid altering in key stringpool
- std::set<std::string> whitelisted_resources;
+ // Set of resources to avoid collapsing to a single entry in key stringpool.
+ std::set<ResourceName> name_collapse_exemptions;
// Map from original resource paths to shortened resource paths.
std::map<std::string, std::string> shortened_path_map;
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index a940923..8fbdd7f 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -518,7 +518,7 @@
ASSERT_FALSE(Flatten(context.get(), {}, table.get(), &result));
}
-TEST_F(TableFlattenerTest, ObfuscatingResourceNamesNoWhitelistSucceeds) {
+TEST_F(TableFlattenerTest, ObfuscatingResourceNamesNoNameCollapseExemptionsSucceeds) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
.SetPackageId("com.app.test", 0x7f)
@@ -572,7 +572,7 @@
ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, (uint32_t)idx, 0u));
}
-TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithWhitelistSucceeds) {
+TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithNameCollapseExemptionsSucceeds) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
.SetPackageId("com.app.test", 0x7f)
@@ -591,21 +591,22 @@
TableFlattenerOptions options;
options.collapse_key_stringpool = true;
- options.whitelisted_resources.insert("test");
- options.whitelisted_resources.insert("three");
+ options.name_collapse_exemptions.insert(ResourceName({}, ResourceType::kId, "one"));
+ options.name_collapse_exemptions.insert(ResourceName({}, ResourceType::kString, "test"));
ResTable res_table;
ASSERT_TRUE(Flatten(context_.get(), options, table.get(), &res_table));
- EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
+ EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one",
ResourceId(0x7f020000), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
ResourceId(0x7f020001), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
- EXPECT_TRUE(Exists(&res_table, "com.app.test:id/three", ResourceId(0x7f020002), {},
- Res_value::TYPE_REFERENCE, 0x7f020000u, 0u));
+ EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
+ ResourceId(0x7f020002), {}, Res_value::TYPE_REFERENCE, 0x7f020000u, 0u));
+ // Note that this resource is also named "one", but it's a different type, so gets obfuscated.
EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated",
ResourceId(0x7f030000), {}, Res_value::TYPE_INT_DEC, 1u,
ResTable_config::CONFIG_VERSION));