diff options
326 files changed, 16068 insertions, 3234 deletions
diff --git a/Android.bp b/Android.bp index 35c7b1bc6fdd..e17a9b45d3ef 100644 --- a/Android.bp +++ b/Android.bp @@ -163,7 +163,7 @@ filegroup { } filegroup { - name: "framework-srcs", + name: "framework-non-updatable-sources", srcs: [ // Java/AIDL sources under frameworks/base ":framework-core-sources", @@ -211,6 +211,14 @@ filegroup { ], } +filegroup { + name: "framework-all-sources", + srcs: [ + ":framework-non-updatable-sources", + ":updatable-media-srcs", + ], +} + java_defaults { name: "framework-aidl-export-defaults", aidl: { @@ -288,15 +296,12 @@ java_defaults { defaults: ["framework-aidl-export-defaults"], installable: true, - srcs: [ - ":framework-srcs", - "core/java/**/*.logtags", - ], - aidl: { generate_get_transaction_name: true, }, + srcs: ["core/java/**/*.logtags"], + exclude_srcs: [ // See comment on framework-atb-backward-compatibility module below "core/java/android/content/pm/AndroidTestBaseUpdater.java", @@ -359,6 +364,7 @@ filegroup { java_library { name: "framework-minus-apex", defaults: ["framework-defaults"], + srcs: [":framework-non-updatable-sources"], javac_shard_size: 150, } @@ -379,8 +385,16 @@ java_library { } java_library { + name: "framework-all", + defaults: ["framework-defaults"], + srcs: [":framework-all-sources"], + installable: false, +} + +java_library { name: "framework-annotation-proc", defaults: ["framework-defaults"], + srcs: [":framework-all-sources"], installable: false, plugins: [ "unsupportedappusage-annotation-processor", @@ -883,10 +897,21 @@ metalava_framework_docs_args += " --replace-documentation " + // replacement (with $1, $2 backreferences to the regex groups) "'$$1https://docs.oracle.com/javase/8/docs/$$2\">' " +packages_to_document = [ + "android", + "java", + "javax", + "org.apache.http", + "org.json", + "org.w3c.dom", + "org.xml.sax", + "org.xmlpull", +] + stubs_defaults { name: "framework-doc-stubs-default", srcs: [ - ":framework-srcs", + ":framework-non-updatable-sources", "core/java/**/*.logtags", "test-base/src/**/*.java", ":opt-telephony-srcs", @@ -951,7 +976,7 @@ doc_defaults { stubs_defaults { name: "metalava-api-stubs-default", srcs: [ - ":framework-srcs", + ":framework-non-updatable-sources", "core/java/**/*.logtags", ":opt-telephony-srcs", ":opt-net-voip-srcs", @@ -974,6 +999,7 @@ stubs_defaults { "api-versions-jars-dir", ], sdk_version: "core_platform", + filter_packages: packages_to_document, } droidstubs { @@ -1472,7 +1498,7 @@ filegroup { // annotations to private apis aidl_mapping { name: "framework-aidl-mappings", - srcs: [":framework-srcs"], + srcs: [":framework-all-sources"], output: "framework-aidl-mappings.txt", } diff --git a/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java b/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java index 2b6f4557303b..9cfc3d272145 100644 --- a/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java +++ b/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java @@ -188,8 +188,8 @@ public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase { @Override public void onAnimationStart(IRecentsAnimationController controller, - RemoteAnimationTarget[] apps, Rect homeContentInsets, - Rect minimizedHomeBounds) throws RemoteException { + RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, + Rect homeContentInsets, Rect minimizedHomeBounds) throws RemoteException { final Pair<String, Boolean> finishCase = finishCases.get(mIteration++ % 2); final boolean moveRecentsToTop = finishCase.second; makeInterval(); diff --git a/api/current.txt b/api/current.txt index 2e099ed8e2a2..b4d110e13261 100644 --- a/api/current.txt +++ b/api/current.txt @@ -2812,12 +2812,12 @@ package android.accessibilityservice { 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 @@ package android.accessibilityservice { 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 @@ package android.media { 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); @@ -52672,7 +52673,7 @@ package android.view.animation { method protected android.view.animation.Animation clone() throws java.lang.CloneNotSupportedException; method public long computeDurationHint(); method protected void ensureInterpolator(); - method @ColorInt public int getBackgroundColor(); + method @Deprecated @ColorInt public int getBackgroundColor(); method @Deprecated public boolean getDetachWallpaper(); method public long getDuration(); method public boolean getFillAfter(); @@ -52696,7 +52697,7 @@ package android.view.animation { method public void restrictDuration(long); method public void scaleCurrentDuration(float); method public void setAnimationListener(android.view.animation.Animation.AnimationListener); - method public void setBackgroundColor(@ColorInt int); + method @Deprecated public void setBackgroundColor(@ColorInt int); method @Deprecated public void setDetachWallpaper(boolean); method public void setDuration(long); method public void setFillAfter(boolean); @@ -53040,6 +53041,7 @@ package android.view.contentcapture { 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 4c48238d655e..283433e519ba 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -8242,6 +8242,7 @@ package android.telephony { 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(); @@ -8673,6 +8674,7 @@ package android.telephony.ims { method public android.os.Bundle getCallExtras(); method public int getCallType(); method public static int getCallTypeFromVideoState(int); + method public int getCallerNumberVerificationStatus(); method public int getEmergencyCallRouting(); method public int getEmergencyServiceCategories(); method @NonNull public java.util.List<java.lang.String> getEmergencyUrns(); @@ -8690,6 +8692,7 @@ package android.telephony.ims { method public void setCallExtraBoolean(String, boolean); method public void setCallExtraInt(String, int); method public void setCallRestrictCause(int); + method public void setCallerNumberVerificationStatus(int); method public void setEmergencyCallRouting(int); method public void setEmergencyCallTesting(boolean); method public void setEmergencyServiceCategories(int); @@ -8740,6 +8743,9 @@ package android.telephony.ims { field public static final int SERVICE_TYPE_EMERGENCY = 2; // 0x2 field public static final int SERVICE_TYPE_NONE = 0; // 0x0 field public static final int SERVICE_TYPE_NORMAL = 1; // 0x1 + field public static final int VERIFICATION_STATUS_FAILED = 2; // 0x2 + field public static final int VERIFICATION_STATUS_NOT_VERIFIED = 0; // 0x0 + field public static final int VERIFICATION_STATUS_PASSED = 1; // 0x1 } public class ImsCallSessionListener { @@ -9514,7 +9520,9 @@ package android.telephony.ims.stub { method public void acknowledgeSmsReport(int, int, int); method public String getSmsFormat(); method public void onReady(); - method public final void onSendSmsResult(int, int, int, int) throws java.lang.RuntimeException; + method @Deprecated public final void onSendSmsResult(int, int, int, int) throws java.lang.RuntimeException; + method public final void onSendSmsResultError(int, int, int, int, int) throws java.lang.RuntimeException; + method public final void onSendSmsResultSuccess(int, int) throws java.lang.RuntimeException; method public final void onSmsReceived(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; @@ -9523,6 +9531,7 @@ package android.telephony.ims.stub { field public static final int DELIVER_STATUS_ERROR_NO_MEMORY = 3; // 0x3 field public static final int DELIVER_STATUS_ERROR_REQUEST_NOT_SUPPORTED = 4; // 0x4 field public static final int DELIVER_STATUS_OK = 1; // 0x1 + field public static final int RESULT_NO_NETWORK_ERROR = -1; // 0xffffffff field public static final int SEND_STATUS_ERROR = 2; // 0x2 field public static final int SEND_STATUS_ERROR_FALLBACK = 4; // 0x4 field public static final int SEND_STATUS_ERROR_RETRY = 3; // 0x3 diff --git a/api/test-current.txt b/api/test-current.txt index 6e28f67e9a68..34312f69116a 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -34,8 +34,8 @@ package android { 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); } } @@ -3335,6 +3335,11 @@ package android.view { field @android.view.ViewDebug.ExportedProperty(flagMapping={@android.view.ViewDebug.FlagToString(mask=0x1, equals=0x1, name="FAKE_HARDWARE_ACCELERATED"), @android.view.ViewDebug.FlagToString(mask=0x2, equals=0x2, name="FORCE_HARDWARE_ACCELERATED"), @android.view.ViewDebug.FlagToString(mask=0x4, equals=0x4, name="WANTS_OFFSET_NOTIFICATIONS"), @android.view.ViewDebug.FlagToString(mask=0x10, equals=0x10, name="SHOW_FOR_ALL_USERS"), @android.view.ViewDebug.FlagToString(mask=android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION, equals=android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION, name="NO_MOVE_ANIMATION"), @android.view.ViewDebug.FlagToString(mask=0x80, equals=0x80, name="COMPATIBLE_WINDOW"), @android.view.ViewDebug.FlagToString(mask=0x100, equals=0x100, name="SYSTEM_ERROR"), @android.view.ViewDebug.FlagToString(mask=0x200, equals=0x200, name="INHERIT_TRANSLUCENT_DECOR"), @android.view.ViewDebug.FlagToString(mask=0x400, equals=0x400, name="KEYGUARD"), @android.view.ViewDebug.FlagToString(mask=0x800, equals=0x800, name="DISABLE_WALLPAPER_TOUCH_EVENTS"), @android.view.ViewDebug.FlagToString(mask=0x1000, equals=0x1000, name="FORCE_STATUS_BAR_VISIBLE_TRANSPARENT"), @android.view.ViewDebug.FlagToString(mask=0x2000, equals=0x2000, name="PRESERVE_GEOMETRY"), @android.view.ViewDebug.FlagToString(mask=0x4000, equals=0x4000, name="FORCE_DECOR_VIEW_VISIBILITY"), @android.view.ViewDebug.FlagToString(mask=0x8000, equals=0x8000, name="WILL_NOT_REPLACE_ON_RELAUNCH"), @android.view.ViewDebug.FlagToString(mask=0x10000, equals=0x10000, name="LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME"), @android.view.ViewDebug.FlagToString(mask=0x20000, equals=0x20000, name="FORCE_DRAW_STATUS_BAR_BACKGROUND"), @android.view.ViewDebug.FlagToString(mask=0x40000, equals=0x40000, name="SUSTAINED_PERFORMANCE_MODE"), @android.view.ViewDebug.FlagToString(mask=0x80000, equals=0x80000, name="HIDE_NON_SYSTEM_OVERLAY_WINDOWS"), @android.view.ViewDebug.FlagToString(mask=0x100000, equals=0x100000, name="IS_ROUNDED_CORNERS_OVERLAY"), @android.view.ViewDebug.FlagToString(mask=0x400000, equals=0x400000, name="IS_SCREEN_DECOR"), @android.view.ViewDebug.FlagToString(mask=0x800000, equals=0x800000, name="STATUS_FORCE_SHOW_NAVIGATION"), @android.view.ViewDebug.FlagToString(mask=0x1000000, equals=0x1000000, name="COLOR_SPACE_AGNOSTIC")}) public int privateFlags; } + public class WindowlessViewRoot { + ctor public WindowlessViewRoot(android.content.Context, android.view.Display, android.view.SurfaceControl); + method public void addView(android.view.View, android.view.WindowManager.LayoutParams); + } + } package android.view.accessibility { diff --git a/cmds/statsd/src/FieldValue.cpp b/cmds/statsd/src/FieldValue.cpp index 1185127ab805..84a06070e431 100644 --- a/cmds/statsd/src/FieldValue.cpp +++ b/cmds/statsd/src/FieldValue.cpp @@ -116,28 +116,13 @@ void translateFieldMatcher(const FieldMatcher& matcher, std::vector<Matcher>* ou } 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 @@ bool isUidField(const Field& field, const Value& value) { 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 b50ec8a84b40..78a6609e5e0d 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -335,6 +335,8 @@ message Atom { 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. @@ -2618,12 +2620,14 @@ message SettingsUIChanged { message TouchEventReported { /** * The fields latency_{min|max|mean|stdev} represent minimum, maximum, mean, - * and the standard deviation of latency between the kernel and framework - * for touchscreen events. The units are microseconds. + * and the standard deviation of the time spent processing touchscreen events + * in the kernel and inputflinger. The units are microseconds. * - * The number is measured as the difference between the time at which - * the input event was received in the evdev driver, - * and the time at which the input event was received in EventHub. + * On supported devices, the starting point is taken during the hard interrupt inside the + * kernel touch driver. On all other devices, the starting point is taken inside + * the kernel's input event subsystem upon receipt of the input event. + * The ending point is taken inside InputDispatcher, just after the input event + * is sent to the app. */ // Minimum value optional float latency_min_micros = 1; @@ -7170,7 +7174,6 @@ message FrameTimingHistogram { repeated int64 frame_counts = 2; } - /** * Information about camera facing and API level usage. * Logged from: @@ -7195,3 +7198,39 @@ message CameraActionEvent { } 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 10ac4a182f87..476fae37899d 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 FieldValueMatcher& matcher, 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 70f0f6f75a59..441d3c896467 100644 --- a/cmds/statsd/tests/LogEntryMatcher_test.cpp +++ b/cmds/statsd/tests/LogEntryMatcher_test.cpp @@ -330,6 +330,7 @@ TEST(AtomMatcherTest, TestUidFieldMatcher) { 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 b4913c87968d..a6e1f0ab0cde 100644 --- a/config/hiddenapi-greylist.txt +++ b/config/hiddenapi-greylist.txt @@ -1180,4 +1180,283 @@ Lcom/android/server/net/BaseNetworkObserver;-><init>()V 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 index 2539051f4069..58a9b64ee9c8 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 index 28c1dea1dbe7..b5401755a3c8 100644 --- a/core/java/android/accessibilityservice/AccessibilityGestureInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java @@ -44,17 +44,17 @@ import java.lang.annotation.Retention; 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 @@ public final class AccessibilityGestureInfo implements Parcelable { /** @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 @@ public final class AccessibilityGestureInfo implements Parcelable { @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 @@ public final class AccessibilityGestureInfo implements Parcelable { /** * @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 a8daf91eadc0..d3c274f4bdfe 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -382,7 +382,7 @@ public abstract class AccessibilityService extends Service { 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 @@ public abstract class AccessibilityService extends Service { } /** - * 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 @@ public abstract class AccessibilityService extends Service { * @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 @@ public abstract class AccessibilityService extends Service { * <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 @@ public abstract class AccessibilityService extends Service { } @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 @@ public abstract class AccessibilityService extends Service { } @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 @@ public abstract class AccessibilityService extends Service { 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 e0d5e4438f23..8ec3aea2728f 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl @@ -20,7 +20,7 @@ import android.accessibilityservice.IAccessibilityServiceConnection; 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 @@ import android.view.KeyEvent; void onInterrupt(); - void onGesture(in AccessibilityGestureInfo gestureInfo); + void onGesture(in AccessibilityGestureEvent gestureEvent); void clearAccessibilityCache(); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index a6784780d72f..1cc849936fde 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -3983,24 +3983,14 @@ public final class ActivityThread extends ClientTransactionHandler { data.info.applicationInfo, data.compatInfo); Service service = null; try { - java.lang.ClassLoader cl = packageInfo.getClassLoader(); - service = packageInfo.getAppFactory() - .instantiateService(cl, data.info.name, data.intent); - } catch (Exception e) { - if (!mInstrumentation.onException(service, e)) { - throw new RuntimeException( - "Unable to instantiate service " + data.info.name - + ": " + e.toString(), e); - } - } - - try { if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name); ContextImpl context = ContextImpl.createAppContext(this, packageInfo); - context.setOuterContext(service); - Application app = packageInfo.makeApplication(false, mInstrumentation); + java.lang.ClassLoader cl = packageInfo.getClassLoader(); + service = packageInfo.getAppFactory() + .instantiateService(cl, data.info.name, data.intent); + context.setOuterContext(service); service.attach(context, this, data.info.name, data.token, app, ActivityManager.getService()); service.onCreate(); diff --git a/core/java/android/app/AppCompatCallbacks.java b/core/java/android/app/AppCompatCallbacks.java index 17697dba9ccd..08c97eb284e3 100644 --- a/core/java/android/app/AppCompatCallbacks.java +++ b/core/java/android/app/AppCompatCallbacks.java @@ -19,6 +19,9 @@ package android.app; 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 @@ import java.util.Arrays; * @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 @@ public final class AppCompatCallbacks extends Compatibility.Callbacks { 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/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 9de42c3b57d5..563174bb9534 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -2671,7 +2671,7 @@ public class AppOpsManager { * @return The proxy UID. */ public int getProxyUid() { - return (int) findFirstNonNegativeForFlagsInStates(mDurations, + return (int) findFirstNonNegativeForFlagsInStates(mProxyUids, MAX_PRIORITY_UID_STATE, MIN_PRIORITY_UID_STATE, OP_FLAGS_ALL); } @@ -2693,7 +2693,7 @@ public class AppOpsManager { * @return The proxy UID. */ public int getProxyUid(@UidState int uidState, @OpFlags int flags) { - return (int) findFirstNonNegativeForFlagsInStates(mDurations, + return (int) findFirstNonNegativeForFlagsInStates(mProxyUids, uidState, uidState, flags); } @@ -4254,8 +4254,8 @@ public class AppOpsManager { * end UID states. * * @param counts The data array. - * @param beginUidState The beginning UID state (exclusive). - * @param endUidState The end UID state. + * @param beginUidState The beginning UID state (inclusive). + * @param endUidState The end UID state (inclusive). * @param flags The UID flags. * @return The sum. */ @@ -4284,13 +4284,13 @@ public class AppOpsManager { * end UID states. * * @param counts The data array. + * @param beginUidState The beginning UID state (inclusive). + * @param endUidState The end UID state (inclusive). * @param flags The UID flags. - * @param beginUidState The beginning UID state (exclusive). - * @param endUidState The end UID state. * @return The non-negative value or -1. */ private static long findFirstNonNegativeForFlagsInStates(@Nullable LongSparseLongArray counts, - @OpFlags int flags, @UidState int beginUidState, @UidState int endUidState) { + @UidState int beginUidState, @UidState int endUidState, @OpFlags int flags) { if (counts == null) { return -1; } @@ -4316,14 +4316,14 @@ public class AppOpsManager { * end UID states. * * @param counts The data array. + * @param beginUidState The beginning UID state (inclusive). + * @param endUidState The end UID state (inclusive). * @param flags The UID flags. - * @param beginUidState The beginning UID state (exclusive). - * @param endUidState The end UID state. * @return The non-negative value or -1. */ private static @Nullable String findFirstNonNullForFlagsInStates( - @Nullable LongSparseArray<String> counts, @OpFlags int flags, - @UidState int beginUidState, @UidState int endUidState) { + @Nullable LongSparseArray<String> counts, @UidState int beginUidState, + @UidState int endUidState, @OpFlags int flags) { if (counts == null) { return null; } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index bb4e99873f26..2f03ed484e96 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -8150,7 +8150,9 @@ public class Notification implements Parcelable Action action, StandardTemplateParams p) { final boolean tombstone = (action.actionIntent == null); container.setViewVisibility(buttonId, View.VISIBLE); - container.setImageViewIcon(buttonId, action.getIcon()); + if (buttonId != R.id.media_seamless) { + container.setImageViewIcon(buttonId, action.getIcon()); + } // If the action buttons should not be tinted, then just use the default // notification color. Otherwise, just use the passed-in color. @@ -8204,6 +8206,10 @@ public class Notification implements Parcelable view.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE); } } + bindMediaActionButton(view, R.id.media_seamless, new Action( + R.drawable.ic_media_seamless, mBuilder.mContext.getString( + com.android.internal.R.string.ext_media_seamless_action), null), p); + view.setViewVisibility(R.id.media_seamless, View.GONE); handleImage(view); // handle the content margin int endMargin = R.dimen.notification_content_margin_end; diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index f9b96c50e0b8..fd93450c29f0 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 @@ public final class UiAutomation { } @Override - public boolean onGesture(AccessibilityGestureInfo gestureInfo) { + public boolean onGesture(AccessibilityGestureEvent gestureEvent) { /* do nothing */ return false; } diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index c74daa8eadfc..3933e81c732a 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -519,9 +519,9 @@ public class LauncherApps { * <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 c561013e6768..8dfe00a8adf6 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -39,7 +39,7 @@ import android.app.PackageInstallObserver; 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 @@ public abstract class PackageManager { * @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 de61d70a9260..24ee21360ed8 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 @@ public abstract class PackageManagerInternal { 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/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java index 271020db0ccd..bcb94ce2d2d5 100644 --- a/core/java/android/os/ParcelFileDescriptor.java +++ b/core/java/android/os/ParcelFileDescriptor.java @@ -79,6 +79,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { /** * Wrapped {@link ParcelFileDescriptor}, if any. Used to avoid * double-closing {@link #mFd}. + * mClosed is always true if mWrapped is non-null. */ private final ParcelFileDescriptor mWrapped; @@ -1023,6 +1024,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { } try { if (!mClosed) { + // mWrapped was and is null. closeWithStatus(Status.LEAKED, null); } } finally { diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 30e59590022c..76e728a11e83 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -182,6 +182,12 @@ public class Process { */ 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/os/SystemProperties.java b/core/java/android/os/SystemProperties.java index cdae72ebd313..b6af82948da3 100644 --- a/core/java/android/os/SystemProperties.java +++ b/core/java/android/os/SystemProperties.java @@ -189,6 +189,8 @@ public class SystemProperties { * Set the value for the given {@code key} to {@code val}. * * @throws IllegalArgumentException if the {@code val} exceeds 91 characters + * @throws RuntimeException if the property cannot be set, for example, if it was blocked by + * SELinux. libc will log the underlying reason. * @hide */ @UnsupportedAppUsage diff --git a/core/java/android/service/carrier/ApnService.java b/core/java/android/service/carrier/ApnService.java index 57e4b1b40748..0c12fd409078 100644 --- a/core/java/android/service/carrier/ApnService.java +++ b/core/java/android/service/carrier/ApnService.java @@ -26,7 +26,7 @@ import android.content.Intent; 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 index 34c9067c3f2f..fadd2ff1a772 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 78e30ab8cdc3..85f13d552fcf 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -1522,6 +1522,7 @@ public abstract class NotificationListenerService extends Service { 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 @@ public abstract class NotificationListenerService extends Service { out.writeTypedList(mSmartActions, flags); out.writeCharSequenceList(mSmartReplies); out.writeBoolean(mCanBubble); + out.writeBoolean(mVisuallyInterruptive); } /** @hide */ @@ -1585,6 +1587,7 @@ public abstract class NotificationListenerService extends Service { mSmartActions = in.createTypedArrayList(Notification.Action.CREATOR); mSmartReplies = in.readCharSequenceList(); mCanBubble = in.readBoolean(); + mVisuallyInterruptive = in.readBoolean(); } @@ -1772,6 +1775,11 @@ public abstract class NotificationListenerService extends Service { } /** @hide */ + public boolean visuallyInterruptive() { + return mVisuallyInterruptive; + } + + /** @hide */ public boolean isNoisy() { return mNoisy; } @@ -1787,7 +1795,8 @@ public abstract class NotificationListenerService extends Service { 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 @@ public abstract class NotificationListenerService extends Service { mSmartActions = smartActions; mSmartReplies = smartReplies; mCanBubble = canBubble; + mVisuallyInterruptive = visuallyInterruptive; } /** @@ -1832,7 +1842,8 @@ public abstract class NotificationListenerService extends Service { 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 86651060a394..1c50d73c4953 100644 --- a/core/java/android/text/TextLine.java +++ b/core/java/android/text/TextLine.java @@ -276,6 +276,7 @@ public class TextLine { 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 @@ public class TextLine { 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 @@ public class TextLine { 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 d2dcb568ef6c..f1d152ba29af 100644 --- a/core/java/android/view/IPinnedStackController.aidl +++ b/core/java/android/view/IPinnedStackController.aidl @@ -16,6 +16,8 @@ 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. @@ -30,15 +32,17 @@ interface IPinnedStackController { 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. + * @return what WM considers to be the current device rotation. */ - oneway void setMinEdgeSize(int minEdgeSize); + int getDisplayRotation(); /** - * @return what WM considers to be the current device rotation. + * 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. */ - int getDisplayRotation(); + 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 2da353b1f0ee..806d81e39cca 100644 --- a/core/java/android/view/IPinnedStackListener.aidl +++ b/core/java/android/view/IPinnedStackListener.aidl @@ -16,8 +16,10 @@ package android.view; +import android.content.ComponentName; import android.content.pm.ParceledListSlice; import android.graphics.Rect; +import android.view.DisplayInfo; import android.view.IPinnedStackController; /** @@ -36,18 +38,13 @@ oneway interface IPinnedStackListener { /** * 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 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). + * 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. */ - void onMovementBoundsChanged(in Rect insetBounds, in Rect normalBounds, in Rect animatingBounds, - boolean fromImeAdjustment, boolean fromShelfAdjustment, int displayRotation); + void onMovementBoundsChanged(in Rect animatingBounds, boolean fromImeAdjustment, + boolean fromShelfAdjustment); /** * Called when window manager decides to adjust the pinned stack bounds because of the IME, or @@ -76,4 +73,47 @@ oneway interface IPinnedStackListener { * 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/IRecentsAnimationRunner.aidl b/core/java/android/view/IRecentsAnimationRunner.aidl index 724d96ed9585..6eb90fc54286 100644 --- a/core/java/android/view/IRecentsAnimationRunner.aidl +++ b/core/java/android/view/IRecentsAnimationRunner.aidl @@ -54,6 +54,6 @@ oneway interface IRecentsAnimationRunner { */ @UnsupportedAppUsage void onAnimationStart(in IRecentsAnimationController controller, - in RemoteAnimationTarget[] apps, in Rect homeContentInsets, - in Rect minimizedHomeBounds) = 2; + in RemoteAnimationTarget[] apps, in RemoteAnimationTarget[] wallpapers, + in Rect homeContentInsets, in Rect minimizedHomeBounds) = 2; } diff --git a/core/java/android/view/IRemoteAnimationRunner.aidl b/core/java/android/view/IRemoteAnimationRunner.aidl index 73b023c99665..7b35aa2d2b45 100644 --- a/core/java/android/view/IRemoteAnimationRunner.aidl +++ b/core/java/android/view/IRemoteAnimationRunner.aidl @@ -34,7 +34,7 @@ oneway interface IRemoteAnimationRunner { * @param finishedCallback The callback to invoke when the animation is finished. */ @UnsupportedAppUsage - void onAnimationStart(in RemoteAnimationTarget[] apps, + void onAnimationStart(in RemoteAnimationTarget[] apps, in RemoteAnimationTarget[] wallpapers, in IRemoteAnimationFinishedCallback finishedCallback); /** diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java index b2f3f5edcd20..d54e9d58356b 100644 --- a/core/java/android/view/NotificationHeaderView.java +++ b/core/java/android/view/NotificationHeaderView.java @@ -30,6 +30,7 @@ import android.graphics.drawable.Drawable; import android.util.ArraySet; import android.util.AttributeSet; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.RemoteViews; import com.android.internal.R; @@ -54,6 +55,7 @@ public class NotificationHeaderView extends ViewGroup { private OnClickListener mExpandClickListener; private OnClickListener mAppOpsListener; private HeaderTouchListener mTouchListener = new HeaderTouchListener(); + private LinearLayout mTransferChip; private ImageView mExpandButton; private CachingIconView mIcon; private View mProfileBadge; @@ -116,6 +118,7 @@ public class NotificationHeaderView extends ViewGroup { mAppName = findViewById(com.android.internal.R.id.app_name_text); mHeaderText = findViewById(com.android.internal.R.id.header_text); mSecondaryHeaderText = findViewById(com.android.internal.R.id.header_text_secondary); + mTransferChip = findViewById(com.android.internal.R.id.media_seamless); mExpandButton = findViewById(com.android.internal.R.id.expand_button); mIcon = findViewById(com.android.internal.R.id.icon); mProfileBadge = findViewById(com.android.internal.R.id.profile_badge); @@ -148,9 +151,11 @@ public class NotificationHeaderView extends ViewGroup { int childHeightSpec = getChildMeasureSpec(wrapContentHeightSpec, lp.topMargin + lp.bottomMargin, lp.height); child.measure(childWidthSpec, childHeightSpec); + // Icons that should go at the end if ((child == mExpandButton && mShowExpandButtonAtEnd) || child == mProfileBadge - || child == mAppOps) { + || child == mAppOps + || child == mTransferChip) { iconWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth(); } else { totalWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth(); @@ -211,9 +216,11 @@ public class NotificationHeaderView extends ViewGroup { int layoutRight; int top = (int) (getPaddingTop() + (ownHeight - childHeight) / 2.0f); int bottom = top + childHeight; + // Icons that should go at the end if ((child == mExpandButton && mShowExpandButtonAtEnd) || child == mProfileBadge - || child == mAppOps) { + || child == mAppOps + || child == mTransferChip) { if (end == getMeasuredWidth()) { layoutRight = end - mContentEndMargin; } else { diff --git a/core/java/android/view/SyncRtSurfaceTransactionApplier.java b/core/java/android/view/SyncRtSurfaceTransactionApplier.java index 85457cb48218..a6536f5d83b7 100644 --- a/core/java/android/view/SyncRtSurfaceTransactionApplier.java +++ b/core/java/android/view/SyncRtSurfaceTransactionApplier.java @@ -30,6 +30,14 @@ import java.util.function.Consumer; */ 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 class SyncRtSurfaceTransactionApplier { } 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,6 +135,105 @@ public class SyncRtSurfaceTransactionApplier { public static class SurfaceParams { + 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); + this.windowCrop = new Rect(windowCrop); + this.layer = layer; + this.cornerRadius = cornerRadius; + this.visible = visible; + } + + /** * Constructs surface parameters to be applied when the current view state gets pushed to * RenderThread. @@ -123,18 +242,27 @@ public class SyncRtSurfaceTransactionApplier { * @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.windowCrop = windowCrop != null ? new Rect(windowCrop) : null; 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/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 85aba3c3f535..3756a7de47c0 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1997,7 +1997,6 @@ public final class ViewRootImpl implements ViewParent, mIsInTraversal = true; mWillDrawSoon = true; boolean windowSizeMayChange = false; - final boolean windowAttributesChanged = mWindowAttributesChanged; WindowManager.LayoutParams lp = mWindowAttributes; int desiredWindowWidth; @@ -2014,10 +2013,6 @@ public final class ViewRootImpl implements ViewParent, ((mViewVisibility == View.VISIBLE) != (viewVisibility == View.VISIBLE)); WindowManager.LayoutParams params = null; - if (mWindowAttributesChanged) { - mWindowAttributesChanged = false; - params = lp; - } CompatibilityInfo compatibilityInfo = mDisplay.getDisplayAdjustments().getCompatibilityInfo(); if (compatibilityInfo.supportsScreen() == mLastInCompatMode) { @@ -2194,16 +2189,6 @@ public final class ViewRootImpl implements ViewParent, } } - if (params != null) { - if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) { - if (!PixelFormat.formatHasAlpha(params.format)) { - params.format = PixelFormat.TRANSLUCENT; - } - } - mAttachInfo.mOverscanRequested = (params.flags - & WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN) != 0; - } - if (mApplyInsetsRequested) { mApplyInsetsRequested = false; mLastOverscanRequested = mAttachInfo.mOverscanRequested; @@ -2259,6 +2244,21 @@ public final class ViewRootImpl implements ViewParent, /* True if surface generation id changes. */ boolean surfaceReplaced = false; + final boolean windowAttributesChanged = mWindowAttributesChanged; + if (windowAttributesChanged) { + mWindowAttributesChanged = false; + params = lp; + } + + if (params != null) { + if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0 + && !PixelFormat.formatHasAlpha(params.format)) { + params.format = PixelFormat.TRANSLUCENT; + } + mAttachInfo.mOverscanRequested = + (params.flags & WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN) != 0; + } + if (mFirst || windowShouldResize || insetsChanged || viewVisibilityChanged || params != null || mForceNextWindowRelayout) { mForceNextWindowRelayout = false; diff --git a/core/java/android/view/WindowlessViewRoot.java b/core/java/android/view/WindowlessViewRoot.java index 75057a26afae..b76e1fad563e 100644 --- a/core/java/android/view/WindowlessViewRoot.java +++ b/core/java/android/view/WindowlessViewRoot.java @@ -21,12 +21,15 @@ import android.content.Context; import android.view.SurfaceControl; import android.view.View; +import android.annotation.TestApi; + /** * Utility class for adding a view hierarchy to a SurfaceControl. * * See WindowlessWmTest for example usage. * @hide */ +@TestApi public class WindowlessViewRoot { ViewRootImpl mViewRoot; WindowlessWindowManager mWm; diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index 59e7bd236907..430fb6dd9770 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -95,8 +95,9 @@ class WindowlessWindowManager implements IWindowSession { public void remove(android.view.IWindow window) {} private boolean isOpaque(WindowManager.LayoutParams attrs) { - if (attrs.surfaceInsets.left != 0 || attrs.surfaceInsets.top != 0 || - attrs.surfaceInsets.right != 0 || attrs.surfaceInsets.bottom != 0) { + if (attrs.surfaceInsets != null && attrs.surfaceInsets.left != 0 || + attrs.surfaceInsets.top != 0 || attrs.surfaceInsets.right != 0 || + attrs.surfaceInsets.bottom != 0) { return false; } return !PixelFormat.formatHasAlpha(attrs.format); @@ -118,9 +119,15 @@ class WindowlessWindowManager implements IWindowSession { "Invalid window token (never added or removed already)"); } SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - t.show(sc).setBufferSize(sc, - requestedWidth + attrs.surfaceInsets.left + attrs.surfaceInsets.right, - requestedHeight + attrs.surfaceInsets.top + attrs.surfaceInsets.bottom) + + final Rect surfaceInsets = attrs.surfaceInsets; + int width = surfaceInsets != null ? + requestedWidth + surfaceInsets.left + surfaceInsets.right : requestedWidth; + int height = surfaceInsets != null ? + requestedHeight + surfaceInsets.top + surfaceInsets.bottom : requestedHeight; + + t.show(sc) + .setBufferSize(sc, width, height) .setOpaque(sc, isOpaque(attrs)) .apply(); outSurfaceControl.copyFrom(sc); diff --git a/core/java/android/view/accessibility/AccessibilityCache.java b/core/java/android/view/accessibility/AccessibilityCache.java index 484d9a1d173b..dc8bf9b5fbae 100644 --- a/core/java/android/view/accessibility/AccessibilityCache.java +++ b/core/java/android/view/accessibility/AccessibilityCache.java @@ -71,7 +71,9 @@ public class AccessibilityCache { 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 @@ public class AccessibilityCache { 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 class AccessibilityCache { } } - 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; + } - // 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); - } + final int windowCount = windowsOfDisplay.size(); + if (windowCount > 0) { + // Careful to return the windows in a decreasing layer order. + SparseArray<AccessibilityWindowInfo> sortedWindows = mTempWindowArray; + sortedWindows.clear(); - return windows; + 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); + } + } + 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 @@ public class AccessibilityCache { 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 @@ public class AccessibilityCache { } } - 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 class AccessibilityCache { 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 4db6f4f808f2..d9fa9f24f1ae 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -28,6 +28,7 @@ import android.os.SystemClock; 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 @@ public final class AccessibilityInteractionClient 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 @@ public final class AccessibilityInteractionClient 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/animation/Animation.java b/core/java/android/view/animation/Animation.java index 3b60aee8f604..b03732a2a7f0 100644 --- a/core/java/android/view/animation/Animation.java +++ b/core/java/android/view/animation/Animation.java @@ -636,9 +636,12 @@ public abstract class Animation implements Cloneable { * * @param bg The background color. If 0, no background. Currently must * be black, with any desired alpha level. + * + * @deprecated None of window animations are running with background color. */ + @Deprecated public void setBackgroundColor(@ColorInt int bg) { - mBackgroundColor = bg; + // The background color is not needed any more, do nothing. } /** @@ -665,6 +668,7 @@ public abstract class Animation implements Cloneable { * * @deprecated All window animations are running with detached wallpaper. */ + @Deprecated public void setDetachWallpaper(boolean detachWallpaper) { } @@ -793,10 +797,13 @@ public abstract class Animation implements Cloneable { /** * Returns the background color behind the animation. + * + * @deprecated None of window animations are running with background color. */ + @Deprecated @ColorInt public int getBackgroundColor() { - return mBackgroundColor; + return 0; } /** @@ -805,6 +812,7 @@ public abstract class Animation implements Cloneable { * * @deprecated All window animations are running with detached wallpaper. */ + @Deprecated public boolean getDetachWallpaper() { return true; } diff --git a/core/java/android/view/contentcapture/ChildContentCaptureSession.java b/core/java/android/view/contentcapture/ChildContentCaptureSession.java index b3b0b72c8799..5e02de451fa3 100644 --- a/core/java/android/view/contentcapture/ChildContentCaptureSession.java +++ b/core/java/android/view/contentcapture/ChildContentCaptureSession.java @@ -89,6 +89,11 @@ final class ChildContentCaptureSession extends ContentCaptureSession { } @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 625fcda76563..cf08c18b019a 100644 --- a/core/java/android/view/contentcapture/ContentCaptureSession.java +++ b/core/java/android/view/contentcapture/ContentCaptureSession.java @@ -439,6 +439,19 @@ public abstract class ContentCaptureSession implements AutoCloseable { 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 1e7440bd5a43..349ef09cf059 100644 --- a/core/java/android/view/contentcapture/MainContentCaptureSession.java +++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java @@ -583,6 +583,11 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } @Override + public void internalNotifySessionLifecycle(boolean started) { + notifySessionLifecycle(mId, started); + } + + @Override boolean isContentCaptureEnabled() { return super.isContentCaptureEnabled() && mManager.isContentCaptureEnabled(); } @@ -637,10 +642,9 @@ public final class MainContentCaptureSession extends ContentCaptureSession { 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 cac75cfd8432..57ce28e5059a 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -2561,7 +2561,8 @@ public class Editor { * @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/com/android/internal/compat/ChangeReporter.java b/core/java/com/android/internal/compat/ChangeReporter.java new file mode 100644 index 000000000000..1ce071bd005a --- /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/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 8978414034ff..9bddd2aa0f1d 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -13325,7 +13325,8 @@ public class BatteryStatsImpl extends BatteryStats { return; } - if (mBatteryStatsHistory.getActiveFile() == null) { + final AtomicFile activeHistoryFile = mBatteryStatsHistory.getActiveFile(); + if (activeHistoryFile == null) { Slog.w(TAG, "readLocked: no history file associated with this instance"); return; @@ -13336,14 +13337,16 @@ public class BatteryStatsImpl extends BatteryStats { Parcel stats = Parcel.obtain(); try { final long start = SystemClock.uptimeMillis(); - byte[] raw = mStatsFile.readFully(); - stats.unmarshall(raw, 0, raw.length); - stats.setDataPosition(0); - readSummaryFromParcel(stats); - if (DEBUG) { - Slog.d(TAG, "readLocked stats file:" + mStatsFile.getBaseFile().getPath() - + " bytes:" + raw.length + " takes ms:" + (SystemClock.uptimeMillis() - - start)); + if (mStatsFile.exists()) { + byte[] raw = mStatsFile.readFully(); + stats.unmarshall(raw, 0, raw.length); + stats.setDataPosition(0); + readSummaryFromParcel(stats); + if (DEBUG) { + Slog.d(TAG, "readLocked stats file:" + mStatsFile.getBaseFile().getPath() + + " bytes:" + raw.length + " takes ms:" + (SystemClock.uptimeMillis() + - start)); + } } } catch (Exception e) { Slog.e(TAG, "Error reading battery statistics", e); @@ -13355,17 +13358,19 @@ public class BatteryStatsImpl extends BatteryStats { Parcel history = Parcel.obtain(); try { final long start = SystemClock.uptimeMillis(); - byte[] raw = mBatteryStatsHistory.getActiveFile().readFully(); - if (raw.length > 0) { - history.unmarshall(raw, 0, raw.length); - history.setDataPosition(0); - readHistoryBuffer(history, true); - } - if (DEBUG) { - Slog.d(TAG, "readLocked history file::" - + mBatteryStatsHistory.getActiveFile().getBaseFile().getPath() - + " bytes:" + raw.length + " takes ms:" + (SystemClock.uptimeMillis() - - start)); + if (activeHistoryFile.exists()) { + byte[] raw = activeHistoryFile.readFully(); + if (raw.length > 0) { + history.unmarshall(raw, 0, raw.length); + history.setDataPosition(0); + readHistoryBuffer(history, true); + } + if (DEBUG) { + Slog.d(TAG, "readLocked history file::" + + activeHistoryFile.getBaseFile().getPath() + + " bytes:" + raw.length + " takes ms:" + (SystemClock.uptimeMillis() + - start)); + } } } catch (Exception e) { Slog.e(TAG, "Error reading battery history", e); diff --git a/core/jni/android_os_SystemProperties.cpp b/core/jni/android_os_SystemProperties.cpp index ab5231425b6d..cfdf26abc25c 100644 --- a/core/jni/android_os_SystemProperties.cpp +++ b/core/jni/android_os_SystemProperties.cpp @@ -108,7 +108,7 @@ void SystemProperties_set(JNIEnv *env, jobject clazz, jstring keyJ, if (!ConvertKeyAndForward(env, keyJ, true, handler)) { // Must have been a failure in SetProperty. jniThrowException(env, "java/lang/RuntimeException", - "failed to set system property"); + "failed to set system property (check logcat for reason)"); } } diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp index 7975c8675954..c380fd5e439f 100644 --- a/core/jni/android_view_InputEventReceiver.cpp +++ b/core/jni/android_view_InputEventReceiver.cpp @@ -83,7 +83,7 @@ private: return mInputConsumer.getChannel()->getName(); } - virtual int handleEvent(int receiveFd, int events, void* data); + virtual int handleEvent(int receiveFd, int events, void* data) override; }; diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto index 3323095a6244..b8c5270ef9d8 100644 --- a/core/proto/android/app/settings_enums.proto +++ b/core/proto/android/app/settings_enums.proto @@ -2402,6 +2402,10 @@ enum PageId { // Note: Gear icon is shown next to gesture navigation preference and opens sensitivity dialog SETTINGS_GESTURE_NAV_BACK_SENSITIVITY_DLG = 1748; + // OPEN: Settings > System > Aware > Aware Display + // CATEGORY: SETTINGS + // OS: Q + SETTINGS_AWARE_DISPLAY = 1750; // ---- End Q Constants, all Q constants go above this line ---- // OPEN: Settings > Network & Internet > Wi-Fi > Click new network // CATEGORY: SETTINGS diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto index 777902578e1b..fd1050393de1 100644 --- a/core/proto/android/server/windowmanagerservice.proto +++ b/core/proto/android/server/windowmanagerservice.proto @@ -196,7 +196,7 @@ message StackProto { repeated TaskProto tasks = 3; optional bool fills_parent = 4; optional .android.graphics.RectProto bounds = 5; - optional bool animation_background_surface_is_dimming = 6; + optional bool animation_background_surface_is_dimming = 6 [deprecated=true]; optional bool defer_removal = 7; optional float minimize_amount = 8; optional bool adjusted_for_ime = 9; diff --git a/core/res/res/drawable/media_seamless_background.xml b/core/res/res/drawable/media_seamless_background.xml new file mode 100644 index 000000000000..aec89e0e8980 --- /dev/null +++ b/core/res/res/drawable/media_seamless_background.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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 + --> +<ripple + xmlns:android="http://schemas.android.com/apk/res/android" + android:color="#1f000000"> + <item android:id="@android:id/background"> + <shape android:shape="rectangle"> + <stroke android:width="1dp" android:color="#1f000000"/> + <corners android:radius="20dp"/> + </shape> + </item> +</ripple> diff --git a/core/res/res/layout/notification_material_media_transfer_action.xml b/core/res/res/layout/notification_material_media_transfer_action.xml new file mode 100644 index 000000000000..98d8f1eee8c9 --- /dev/null +++ b/core/res/res/layout/notification_material_media_transfer_action.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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 + --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:visibility="gone" + android:padding="4dp" + android:layout_marginStart="10dp" + android:gravity="center" + android:background="@drawable/media_seamless_background"> + <ImageView + android:layout_width="?attr/notificationHeaderIconSize" + android:layout_height="?attr/notificationHeaderIconSize" + android:src="@drawable/ic_media_seamless" + android:id="@+id/media_seamless_image" /> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?attr/notificationHeaderTextAppearance" + android:text="@string/ext_media_seamless_action" + android:id="@+id/media_seamless_text" + android:paddingEnd="2dp" /> +</LinearLayout>
\ No newline at end of file diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml index 0f53549a966c..f5fa1b6a795a 100644 --- a/core/res/res/layout/notification_template_header.xml +++ b/core/res/res/layout/notification_template_header.xml @@ -175,5 +175,9 @@ android:contentDescription="@string/notification_appops_overlay_active" /> </LinearLayout> + <include + layout="@layout/notification_material_media_transfer_action" + android:id="@+id/media_seamless" + /> </NotificationHeaderView> diff --git a/core/res/res/layout/notification_template_material_big_media.xml b/core/res/res/layout/notification_template_material_big_media.xml index 3267f726c69d..6c47c2c51601 100644 --- a/core/res/res/layout/notification_template_material_big_media.xml +++ b/core/res/res/layout/notification_template_material_big_media.xml @@ -80,10 +80,6 @@ layout="@layout/notification_material_media_action" android:id="@+id/action4" /> - <include - layout="@layout/notification_material_media_action" - android:id="@+id/media_seamless" - /> </LinearLayout> <ViewStub android:id="@+id/notification_media_seekbar_container" android:layout_width="match_parent" diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 18d77067e2b3..d2989d9a8ab6 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -5015,9 +5015,9 @@ <string name="importance_from_person">This is important because of the people involved.</string> <!-- Message to user that app trying to create user for an account that already exists. [CHAR LIMIT=none] --> - <string name="user_creation_account_exists">Allow <xliff:g id="app" example="Gmail">%1$s</xliff:g> to create a new User with <xliff:g id="account" example="foobar@gmail.com">%2$s</xliff:g> ?</string> + <string name="user_creation_account_exists">Allow <xliff:g id="app" example="Gmail">%1$s</xliff:g> to create a new User with <xliff:g id="account" example="foobar@gmail.com">%2$s</xliff:g> (a User with this account already exists) ?</string> <!-- Message to user that app is trying to create user for a specified account. [CHAR LIMIT=none] --> - <string name="user_creation_adding">Allow <xliff:g id="app" example="Gmail">%1$s</xliff:g> to create a new User with <xliff:g id="account" example="foobar">%2$s</xliff:g> (a User with this account already exists) ?</string> + <string name="user_creation_adding">Allow <xliff:g id="app" example="Gmail">%1$s</xliff:g> to create a new User with <xliff:g id="account" example="foobar@gmail.com">%2$s</xliff:g> ?</string> <!-- Locale picker strings --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 8aa4a8cc01b5..761e02fe5dad 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -206,6 +206,8 @@ <java-symbol type="id" name="action3" /> <java-symbol type="id" name="action4" /> <java-symbol type="id" name="media_seamless" /> + <java-symbol type="id" name="media_seamless_image" /> + <java-symbol type="id" name="media_seamless_text" /> <java-symbol type="id" name="notification_media_seekbar_container" /> <java-symbol type="id" name="notification_media_content" /> <java-symbol type="id" name="notification_media_progress" /> diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index 1670d49a46c4..a4c504b9cbdf 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -43,7 +43,6 @@ android_test { 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 1a22a70eed36..6bce6517a85d 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.never; 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 @@ import java.util.List; 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 @@ public class AccessibilityCacheTest { @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 @@ public class AccessibilityCacheTest { @Test public void testEmptyCache_returnsNull() { assertNull(mAccessibilityCache.getNode(0, 0)); - assertNull(mAccessibilityCache.getWindows()); + assertNull(mAccessibilityCache.getWindowsOnAllDisplays()); assertNull(mAccessibilityCache.getWindow(0)); } @@ -114,10 +121,11 @@ public class AccessibilityCacheTest { 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 @@ public class AccessibilityCacheTest { @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 @@ public class AccessibilityCacheTest { } @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 @@ public class AccessibilityCacheTest { @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 @@ public class AccessibilityCacheTest { } } - 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 @@ public class AccessibilityCacheTest { } } } + + 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 81ce15a4d8d2..c5da54936653 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 @@ public class ContentCaptureSessionTest { } @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/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java index c44b7d81868d..cc6eeedaab46 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java @@ -481,6 +481,36 @@ public class ChooserActivityTest { } @Test + public void copyTextToClipboardLogging() throws Exception { + Intent sendIntent = createSendTextIntent(); + List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); + + when(ChooserWrapperActivity.sOverrides.resolverListController.getResolversForIntent( + Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(resolvedComponentInfos); + + MetricsLogger mockLogger = sOverrides.metricsLogger; + ArgumentCaptor<LogMaker> logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class); + + final ChooserWrapperActivity activity = mActivityRule + .launchActivity(Intent.createChooser(sendIntent, null)); + waitForIdle(); + + onView(withId(R.id.copy_button)).check(matches(isDisplayed())); + onView(withId(R.id.copy_button)).perform(click()); + + verify(mockLogger, atLeastOnce()).write(logMakerCaptor.capture()); + // First is Activity shown, Second is "with preview" + assertThat(logMakerCaptor.getAllValues().get(2).getCategory(), + is(MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SYSTEM_TARGET)); + assertThat(logMakerCaptor + .getAllValues().get(2) + .getSubtype(), + is(1)); + } + + @Test public void oneVisibleImagePreview() throws InterruptedException { Uri uri = Uri.parse("android.resource://com.android.frameworks.coretests/" + com.android.frameworks.coretests.R.drawable.test320x240); diff --git a/keystore/java/android/security/Credentials.java b/keystore/java/android/security/Credentials.java index 08f417662523..54995ac9d050 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 class Credentials { 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 class Credentials { 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 b3cdff7eedf7..97da3cc6f80f 100644 --- a/keystore/java/android/security/IKeyChainService.aidl +++ b/keystore/java/android/security/IKeyChainService.aidl @@ -43,7 +43,8 @@ interface IKeyChainService { 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/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index b5ef8f43dfb6..684dc22ee4d1 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -146,7 +146,7 @@ void CanvasContext::setSurface(sp<Surface>&& surface) { if (surface) { mNativeSurface = new ReliableSurface{std::move(surface)}; // TODO: Fix error handling & re-shorten timeout - mNativeSurface->setDequeueTimeout(4000_ms); + ANativeWindow_setDequeueTimeout(mNativeSurface.get(), 4000_ms); mNativeSurface->enableFrameTimestamps(true); } else { mNativeSurface = nullptr; diff --git a/libs/hwui/renderthread/ReliableSurface.h b/libs/hwui/renderthread/ReliableSurface.h index 0d251b1f10e7..f768df37ba7d 100644 --- a/libs/hwui/renderthread/ReliableSurface.h +++ b/libs/hwui/renderthread/ReliableSurface.h @@ -31,8 +31,6 @@ public: ReliableSurface(sp<Surface>&& surface); ~ReliableSurface(); - void setDequeueTimeout(nsecs_t timeout) { mSurface->setDequeueTimeout(timeout); } - int reserveNext(); void allocateBuffers() { mSurface->allocateBuffers(); } diff --git a/location/lib/Android.bp b/location/lib/Android.bp index ff6921d3e1c2..b36aa036daba 100644 --- a/location/lib/Android.bp +++ b/location/lib/Android.bp @@ -18,7 +18,7 @@ java_sdk_library { name: "com.android.location.provider", srcs: [ "java/**/*.java", - ":framework-srcs", + ":framework-all-sources", ], libs: [ "androidx.annotation_annotation", diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index 55583d58e0c2..5b535651abd9 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -62,6 +62,7 @@ import java.util.Arrays; 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 @@ public class ExifInterface { } /** + * 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_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index cbc820b6fde0..05aaa82f8ac8 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -745,6 +745,8 @@ status_t JMediaCodec::getCodecInfo(JNIEnv *env, jobject *codecInfoObject) const status_t JMediaCodec::getMetrics(JNIEnv *, MediaAnalyticsItem * &reply) const { mediametrics_handle_t reply2 = MediaAnalyticsItem::convert(reply); status_t status = mCodec->getMetrics(reply2); + // getMetrics() updates reply2, pass the converted update along to our caller. + reply = MediaAnalyticsItem::convert(reply2); return status; } diff --git a/media/lib/signer/Android.bp b/media/lib/signer/Android.bp index 85a007f427b8..2286c5379e1a 100644 --- a/media/lib/signer/Android.bp +++ b/media/lib/signer/Android.bp @@ -18,7 +18,7 @@ java_sdk_library { name: "com.android.mediadrm.signer", srcs: [ "java/**/*.java", - ":framework-srcs", + ":framework-all-sources", ], api_packages: ["com.android.mediadrm.signer"], } diff --git a/core/proto/android/server/backup_chunks_metadata.proto b/packages/BackupEncryption/proto/backup_chunks_metadata.proto index a375f02545c5..2fdedbf70975 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/chunk/Chunk.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/Chunk.java deleted file mode 100644 index ba328609a77e..000000000000 --- 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 index a44890118717..51d7d532c920 100644 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkListingMap.java +++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkListingMap.java @@ -17,51 +17,41 @@ 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 com.android.server.backup.encryption.protos.nano.ChunksMetadataProto; + 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 + * 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(); + + private final Map<ChunkHash, Entry> mChunksByHash; + + /** Construct a map from a {@link ChunksMetadataProto.ChunkListing} protobuf */ + public static ChunkListingMap fromProto(ChunksMetadataProto.ChunkListing chunkListingProto) { + 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); - } + for (ChunksMetadataProto.Chunk chunk : chunkListingProto.chunks) { + entries.put(new ChunkHash(chunk.hash), new Entry(start, chunk.length)); + start += chunk.length; } return new ChunkListingMap(entries); } - private final Map<ChunkHash, Entry> mChunksByHash; - private ChunkListingMap(Map<ChunkHash, Entry> chunksByHash) { - mChunksByHash = Collections.unmodifiableMap(new HashMap<>(chunksByHash)); + // This is only called from the {@link #fromProto} method, so we don't + // need to take a copy. + this.mChunksByHash = chunksByHash; } /** Returns {@code true} if there is a chunk with the given SHA-256 MAC key in the listing. */ @@ -81,19 +71,14 @@ public class ChunkListingMap { 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; + mStart = start; } /** Returns the length of the chunk in bytes. */ 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 8cb028e46e9d..9cda3395f79a 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 7b38dd4a1dc3..6b9be9fd91d3 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 567f75d59513..e707350505fb 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/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 index 24e5573b891d..c5f78c254cd1 100644 --- 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 @@ -19,10 +19,8 @@ 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.android.server.backup.encryption.protos.nano.ChunksMetadataProto; import com.google.common.base.Charsets; @@ -31,167 +29,86 @@ 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 ChunkHash CHUNK_A_HASH = getHash("CHUNK_A"); + private static final ChunkHash CHUNK_B_HASH = getHash("CHUNK_B"); + private static final ChunkHash CHUNK_C_HASH = getHash("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; + private static final int CHUNK_A_START = 0; + private static final int CHUNK_B_START = CHUNK_A_START + CHUNK_A_LENGTH; + private static final int CHUNK_C_START = CHUNK_B_START + CHUNK_B_LENGTH; + + private ChunkListingMap mChunkListingMap; @Before - public void setUp() throws Exception { - mChunkHashA = getHash(CHUNK_A); - mChunkHashB = getHash(CHUNK_B); - mChunkHashC = getHash(CHUNK_C); + public void setUp() { + mChunkListingMap = createFromFixture(); } @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(); + public void hasChunk_isTrueForExistingChunks() { + assertThat(mChunkListingMap.hasChunk(CHUNK_A_HASH)).isTrue(); + assertThat(mChunkListingMap.hasChunk(CHUNK_B_HASH)).isTrue(); + assertThat(mChunkListingMap.hasChunk(CHUNK_C_HASH)).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(); + public void hasChunk_isFalseForNonexistentChunks() { + assertThat(mChunkListingMap.hasChunk(getHash("CHUNK_D"))).isFalse(); + assertThat(mChunkListingMap.hasChunk(getHash(""))).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); + public void getChunkListing_hasCorrectLengths() { + assertThat(mChunkListingMap.getChunkEntry(CHUNK_A_HASH).getLength()) + .isEqualTo(CHUNK_A_LENGTH); + assertThat(mChunkListingMap.getChunkEntry(CHUNK_B_HASH).getLength()) + .isEqualTo(CHUNK_B_LENGTH); + assertThat(mChunkListingMap.getChunkEntry(CHUNK_C_HASH).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); + public void getChunkListing_hasCorrectStarts() { + assertThat(mChunkListingMap.getChunkEntry(CHUNK_A_HASH).getStart()) + .isEqualTo(CHUNK_A_START); + assertThat(mChunkListingMap.getChunkEntry(CHUNK_B_HASH).getStart()) + .isEqualTo(CHUNK_B_START); + assertThat(mChunkListingMap.getChunkEntry(CHUNK_C_HASH).getStart()) + .isEqualTo(CHUNK_C_START); } @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(); + public void getChunkListing_isNullForNonExistentChunks() { + assertThat(mChunkListingMap.getChunkEntry(getHash("Hey"))).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 static ChunkListingMap createFromFixture() { + ChunksMetadataProto.ChunkListing chunkListing = new ChunksMetadataProto.ChunkListing(); + chunkListing.chunks = new ChunksMetadataProto.Chunk[3]; + chunkListing.chunks[0] = newChunk(CHUNK_A_HASH.getHash(), CHUNK_A_LENGTH); + chunkListing.chunks[1] = newChunk(CHUNK_B_HASH.getHash(), CHUNK_B_LENGTH); + chunkListing.chunks[2] = newChunk(CHUNK_C_HASH.getHash(), CHUNK_C_LENGTH); + return ChunkListingMap.fromProto(chunkListing); } - 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) { + private static ChunkHash getHash(String name) { return new ChunkHash( Arrays.copyOf(name.getBytes(Charsets.UTF_8), ChunkHash.HASH_LENGTH_BYTES)); } + + public static ChunksMetadataProto.Chunk newChunk(byte[] hash, int length) { + ChunksMetadataProto.Chunk newChunk = new ChunksMetadataProto.Chunk(); + newChunk.hash = Arrays.copyOf(hash, hash.length); + newChunk.length = length; + return newChunk; + } } 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 1796f56ce17a..000000000000 --- 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 634acdc42eaf..7e1fdedcac80 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 static org.mockito.Mockito.mock; 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 d231603e18b1..6f58ee148b83 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 static org.mockito.Mockito.mock; 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/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java index 27146fbac789..264b7d52c02f 100644 --- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java +++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java @@ -25,8 +25,6 @@ import dagger.Component; modules = { DependencyProvider.class, DependencyBinder.class, - ServiceBinder.class, - SystemUIBinder.class, SystemUIFactory.ContextHolder.class, SystemUIModule.class, CarSystemUIModule.class diff --git a/packages/CarrierDefaultApp/tests/unit/Android.bp b/packages/CarrierDefaultApp/tests/unit/Android.bp index 96144cf76ff7..5655abb046b2 100644 --- a/packages/CarrierDefaultApp/tests/unit/Android.bp +++ b/packages/CarrierDefaultApp/tests/unit/Android.bp @@ -17,7 +17,6 @@ android_test { certificate: "platform", libs: [ "android.test.runner", - "telephony-common", "android.test.base", ], static_libs: [ diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java index 8c0108dace69..602fe3ec12fd 100644 --- a/packages/Shell/src/com/android/shell/BugreportProgressService.java +++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java @@ -1264,9 +1264,7 @@ public class BugreportProgressService extends Service { } 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/Android.bp b/packages/SystemUI/Android.bp index d674be4c8fc0..37fefc2d37d7 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -76,6 +76,16 @@ android_library { plugins: ["dagger2-compiler-2.19"], } +filegroup { + name: "SystemUI-tests-utils", + srcs: [ + "tests/src/com/android/systemui/statusbar/NotificationEntryBuilder.java", + "tests/src/com/android/systemui/statusbar/RankingBuilder.java", + "tests/src/com/android/systemui/statusbar/SbnBuilder.java", + ], + path: "tests/src", +} + android_library { name: "SystemUI-tests", manifest: "tests/AndroidManifest.xml", 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 60435d0dec35..d62c1d411cff 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 @@ import com.android.systemui.plugins.annotations.ProvidesInterface; /** * 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/layout/auth_biometric_contents.xml b/packages/SystemUI/res/layout/auth_biometric_contents.xml index 925e4fad103d..a1006a8396e0 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/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java index 82287873f5ad..00e8b535ccac 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java @@ -225,14 +225,16 @@ public class ActivityManagerWrapper { runner = new IRecentsAnimationRunner.Stub() { @Override public void onAnimationStart(IRecentsAnimationController controller, - RemoteAnimationTarget[] apps, Rect homeContentInsets, - Rect minimizedHomeBounds) { + RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, + Rect homeContentInsets, Rect minimizedHomeBounds) { final RecentsAnimationControllerCompat controllerCompat = new RecentsAnimationControllerCompat(controller); final RemoteAnimationTargetCompat[] appsCompat = RemoteAnimationTargetCompat.wrap(apps); + final RemoteAnimationTargetCompat[] wallpapersCompat = + RemoteAnimationTargetCompat.wrap(wallpapers); animationHandler.onAnimationStart(controllerCompat, appsCompat, - homeContentInsets, minimizedHomeBounds); + wallpapersCompat, homeContentInsets, minimizedHomeBounds); } @Override 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 3ae2df5b97bf..2797042ac160 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,9 +16,10 @@ package com.android.systemui.shared.system; +import android.content.ComponentName; import android.content.pm.ParceledListSlice; import android.graphics.Rect; -import android.os.RemoteException; +import android.view.DisplayInfo; import android.view.IPinnedStackController; import android.view.IPinnedStackListener; @@ -32,62 +33,132 @@ import java.util.List; * previously set listener. */ public class PinnedStackListenerForwarder extends IPinnedStackListener.Stub { - private List<IPinnedStackListener> mListeners = new ArrayList<>(); + private List<PinnedStackListener> mListeners = new ArrayList<>(); /** Adds a listener to receive updates from the WindowManagerService. */ - public void addListener(IPinnedStackListener listener) { + public void addListener(PinnedStackListener listener) { mListeners.add(listener); } /** Removes a listener so it will no longer receive updates from the WindowManagerService. */ - public void removeListener(IPinnedStackListener listener) { + public void removeListener(PinnedStackListener listener) { mListeners.remove(listener); } @Override - public void onListenerRegistered(IPinnedStackController controller) throws RemoteException { - for (IPinnedStackListener listener : mListeners) { + public void onListenerRegistered(IPinnedStackController controller) { + for (PinnedStackListener listener : mListeners) { listener.onListenerRegistered(controller); } } @Override - 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); + public void onMovementBoundsChanged(Rect animatingBounds, boolean fromImeAdjustment, + boolean fromShelfAdjustment) { + for (PinnedStackListener listener : mListeners) { + listener.onMovementBoundsChanged(animatingBounds, fromImeAdjustment, + fromShelfAdjustment); } } @Override - public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) throws RemoteException { - for (IPinnedStackListener listener : mListeners) { + public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) { + for (PinnedStackListener listener : mListeners) { listener.onImeVisibilityChanged(imeVisible, imeHeight); } } @Override - public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) - throws RemoteException { - for (IPinnedStackListener listener : mListeners) { + public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) { + for (PinnedStackListener listener : mListeners) { listener.onShelfVisibilityChanged(shelfVisible, shelfHeight); } } @Override - public void onMinimizedStateChanged(boolean isMinimized) throws RemoteException { - for (IPinnedStackListener listener : mListeners) { + public void onMinimizedStateChanged(boolean isMinimized) { + for (PinnedStackListener listener : mListeners) { listener.onMinimizedStateChanged(isMinimized); } } @Override - public void onActionsChanged(ParceledListSlice actions) throws RemoteException { - for (IPinnedStackListener listener : mListeners) { + public void onActionsChanged(ParceledListSlice actions) { + for (PinnedStackListener 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/RecentsAnimationListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java index 579858a4f9b4..2c99c5c84015 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java @@ -21,12 +21,12 @@ import android.graphics.Rect; import com.android.systemui.shared.recents.model.ThumbnailData; public interface RecentsAnimationListener { - /** * Called when the animation into Recents can start. This call is made on the binder thread. */ void onAnimationStart(RecentsAnimationControllerCompat controller, - RemoteAnimationTargetCompat[] apps, Rect homeContentInsets, Rect minimizedHomeBounds); + RemoteAnimationTargetCompat[] apps, RemoteAnimationTargetCompat[] wallpapers, + Rect homeContentInsets, Rect minimizedHomeBounds); /** * Called when the animation into Recents was canceled. This call is made on the binder thread. diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java index 61be076ac48a..02e509eef25f 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java @@ -45,9 +45,12 @@ public class RemoteAnimationAdapterCompat { return new IRemoteAnimationRunner.Stub() { @Override public void onAnimationStart(RemoteAnimationTarget[] apps, + RemoteAnimationTarget[] wallpapers, final IRemoteAnimationFinishedCallback finishedCallback) { final RemoteAnimationTargetCompat[] appsCompat = RemoteAnimationTargetCompat.wrap(apps); + final RemoteAnimationTargetCompat[] wallpapersCompat = + RemoteAnimationTargetCompat.wrap(wallpapers); final Runnable animationFinishedCallback = new Runnable() { @Override public void run() { @@ -59,7 +62,8 @@ public class RemoteAnimationAdapterCompat { } } }; - remoteAnimationAdapter.onAnimationStart(appsCompat, animationFinishedCallback); + remoteAnimationAdapter.onAnimationStart(appsCompat, wallpapersCompat, + animationFinishedCallback); } @Override diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java index 5a85df967197..33372f6bd0b9 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java @@ -17,6 +17,7 @@ package com.android.systemui.shared.system; public interface RemoteAnimationRunnerCompat { - void onAnimationStart(RemoteAnimationTargetCompat[] apps, Runnable finishedCallback); + void onAnimationStart(RemoteAnimationTargetCompat[] apps, + RemoteAnimationTargetCompat[] wallpapers, Runnable finishedCallback); void onAnimationCancelled(); }
\ No newline at end of file diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java index 221782e950d0..ca88f13932ad 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java @@ -68,7 +68,7 @@ public class RemoteAnimationTargetCompat { public static RemoteAnimationTargetCompat[] wrap(RemoteAnimationTarget[] apps) { final RemoteAnimationTargetCompat[] appsCompat = - new RemoteAnimationTargetCompat[apps.length]; + new RemoteAnimationTargetCompat[apps != null ? apps.length : 0]; for (int i = 0; i < apps.length; i++) { appsCompat[i] = new RemoteAnimationTargetCompat(apps[i]); } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java index 9ba21a328ca4..e80b43739557 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java @@ -142,7 +142,9 @@ public class SyncRtSurfaceTransactionApplierCompat { public static void applyParams(TransactionCompat t, SyncRtSurfaceTransactionApplierCompat.SurfaceParams params) { t.setMatrix(params.surface, params.matrix); - t.setWindowCrop(params.surface, params.windowCrop); + if (params.windowCrop != null) { + t.setWindowCrop(params.surface, params.windowCrop); + } t.setAlpha(params.surface, params.alpha); t.setLayer(params.surface, params.layer); t.setCornerRadius(params.surface, params.cornerRadius); @@ -187,14 +189,14 @@ public class SyncRtSurfaceTransactionApplierCompat { * @param surface The surface to modify. * @param alpha Alpha to apply. * @param matrix Matrix to apply. - * @param windowCrop Crop to apply. + * @param windowCrop Crop to apply, only applied if not {@code null} */ public SurfaceParams(SurfaceControlCompat surface, float alpha, Matrix matrix, Rect windowCrop, int layer, float cornerRadius) { this.surface = surface; this.alpha = alpha; this.matrix = new Matrix(matrix); - this.windowCrop = new Rect(windowCrop); + this.windowCrop = windowCrop != null ? new Rect(windowCrop) : null; this.layer = layer; this.cornerRadius = cornerRadius; } 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 794c30a7c7c1..9f1a1fafeec6 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.graphics.Rect; 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 @@ public class WindowManagerWrapper { * 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(IPinnedStackListener listener) throws RemoteException { + public void addPinnedStackListener(PinnedStackListener listener) throws RemoteException { mPinnedStackListenerForwarder.addListener(listener); WindowManagerGlobal.getWindowManagerService().registerPinnedStackListener( DEFAULT_DISPLAY, mPinnedStackListenerForwarder); @@ -221,7 +221,7 @@ public class WindowManagerWrapper { /** * Removes a pinned stack listener. */ - public void removePinnedStackListener(IPinnedStackListener listener) { + public void removePinnedStackListener(PinnedStackListener listener) { mPinnedStackListenerForwarder.removeListener(listener); } } diff --git a/packages/SystemUI/src/com/android/systemui/ActivityBinder.java b/packages/SystemUI/src/com/android/systemui/ActivityBinder.java new file mode 100644 index 000000000000..2c8a67270d94 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/ActivityBinder.java @@ -0,0 +1,44 @@ +/* + * 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; + +import android.app.Activity; + +import com.android.systemui.tuner.TunerActivity; + +import dagger.Binds; +import dagger.Module; +import dagger.multibindings.ClassKey; +import dagger.multibindings.IntoMap; + +/** + * Services and Activities that are injectable should go here. + */ +@Module +public abstract class ActivityBinder { + /** Inject into TunerActivity. */ + @Binds + @IntoMap + @ClassKey(TunerActivity.class) + public abstract Activity bindTunerActivity(TunerActivity activity); + + /** Inject into ForegroundServicesDialog. */ + @Binds + @IntoMap + @ClassKey(ForegroundServicesDialog.class) + public abstract Activity bindForegroundServicesDialog(ForegroundServicesDialog activity); +} diff --git a/packages/SystemUI/src/com/android/systemui/ComponentBinder.java b/packages/SystemUI/src/com/android/systemui/ComponentBinder.java new file mode 100644 index 000000000000..3b35c61e8eb2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/ComponentBinder.java @@ -0,0 +1,31 @@ +/* + * 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; + +import dagger.Binds; +import dagger.Module; + +/** + * Dagger Module that collects related sub-modules together. + */ +@Module(includes = {ActivityBinder.class, ServiceBinder.class, SystemUIBinder.class}) +public abstract class ComponentBinder { + /** */ + @Binds + public abstract ContextComponentHelper bindComponentHelper( + ContextComponentResolver componentHelper); +} diff --git a/packages/SystemUI/src/com/android/systemui/ContextComponentHelper.java b/packages/SystemUI/src/com/android/systemui/ContextComponentHelper.java index 5fe5792219c3..2cf0f8dafcad 100644 --- a/packages/SystemUI/src/com/android/systemui/ContextComponentHelper.java +++ b/packages/SystemUI/src/com/android/systemui/ContextComponentHelper.java @@ -16,6 +16,7 @@ package com.android.systemui; +import android.app.Activity; import android.app.Service; /** @@ -23,6 +24,9 @@ import android.app.Service; */ public interface ContextComponentHelper { /** Turns a classname into an instance of the class or returns null. */ + Activity resolveActivity(String className); + + /** Turns a classname into an instance of the class or returns null. */ Service resolveService(String className); /** Turns a classname into an instance of the class or returns null. */ diff --git a/packages/SystemUI/src/com/android/systemui/ContextComponentResolver.java b/packages/SystemUI/src/com/android/systemui/ContextComponentResolver.java index cee21c167fa0..995263240e2d 100644 --- a/packages/SystemUI/src/com/android/systemui/ContextComponentResolver.java +++ b/packages/SystemUI/src/com/android/systemui/ContextComponentResolver.java @@ -16,6 +16,7 @@ package com.android.systemui; +import android.app.Activity; import android.app.Service; import java.util.Map; @@ -29,18 +30,29 @@ import javax.inject.Singleton; */ @Singleton public class ContextComponentResolver implements ContextComponentHelper { + private final Map<Class<?>, Provider<Activity>> mActivityCreators; private final Map<Class<?>, Provider<Service>> mServiceCreators; private final Map<Class<?>, Provider<SystemUI>> mSystemUICreators; @Inject ContextComponentResolver( + Map<Class<?>, Provider<Activity>> activityCreators, Map<Class<?>, Provider<Service>> serviceCreators, Map<Class<?>, Provider<SystemUI>> systemUICreators) { + mActivityCreators = activityCreators; mServiceCreators = serviceCreators; mSystemUICreators = systemUICreators; } /** + * Looks up the Activity class name to see if Dagger has an instance of it. + */ + @Override + public Activity resolveActivity(String className) { + return resolve(className, mActivityCreators); + } + + /** * Looks up the Service class name to see if Dagger has an instance of it. */ @Override @@ -57,12 +69,12 @@ public class ContextComponentResolver implements ContextComponentHelper { } private <T> T resolve(String className, Map<Class<?>, Provider<T>> creators) { - for (Map.Entry<Class<?>, Provider<T>> p : creators.entrySet()) { - if (p.getKey().getName().equals(className)) { - return p.getValue().get(); - } + try { + Class<?> clazz = Class.forName(className); + Provider<T> provider = creators.get(clazz); + return provider == null ? null : provider.get(); + } catch (ClassNotFoundException e) { + return null; } - - return null; } } diff --git a/packages/SystemUI/src/com/android/systemui/CornerHandleView.java b/packages/SystemUI/src/com/android/systemui/CornerHandleView.java index 6209c2c36206..a94952c5bc19 100644 --- a/packages/SystemUI/src/com/android/systemui/CornerHandleView.java +++ b/packages/SystemUI/src/com/android/systemui/CornerHandleView.java @@ -19,7 +19,6 @@ package com.android.systemui; import android.animation.ArgbEvaluator; import android.content.Context; import android.graphics.Canvas; -import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; @@ -28,7 +27,6 @@ import android.util.DisplayMetrics; import android.view.ContextThemeWrapper; import android.view.View; -import com.android.internal.graphics.ColorUtils; import com.android.settingslib.Utils; /** @@ -109,7 +107,6 @@ public class CornerHandleView extends View { mPaint.setColor((int) ArgbEvaluator.getInstance().evaluate(darkIntensity, mLightColor, mDarkColor)); - updateShadow(); if (getVisibility() == VISIBLE) { invalidate(); } @@ -121,21 +118,6 @@ public class CornerHandleView extends View { canvas.drawPath(mPath, mPaint); } - private void updateShadow() { - if (ColorUtils.calculateLuminance(mPaint.getColor()) > 0.7f) { - mPaint.setShadowLayer(/** radius */ 5,/** shadowDx */ 0, /** shadowDy */ -1, - /** color */ ColorUtils.setAlphaComponent(/** color */ Color.BLACK, - /** alpha */ 102)); - } else { - mPaint.setShadowLayer(/** radius */ 0, /** shadowDx */ 0, /** shadowDy */ 0, - /** color */ Color.TRANSPARENT); - } - - if (getVisibility() == VISIBLE) { - invalidate(); - } - } - private static float convertDpToPixel(float dp, Context context) { return dp * ((float) context.getResources().getDisplayMetrics().densityDpi / DisplayMetrics.DENSITY_DEFAULT); diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index bd5b9c724eb3..7771f8655128 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.UserSwitcherController; 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,24 +315,15 @@ public class Dependency { @Inject Lazy<INotificationManager> mINotificationManager; @Inject Lazy<FalsingManager> mFalsingManager; @Inject Lazy<SysUiState> mSysUiStateFlagsContainer; + @Inject Lazy<AlarmManager> mAlarmManager; @Inject public Dependency() { } - /** * Initialize Depenency. */ - public static void initDependencies(SystemUIRootComponent rootComponent) { - if (sDependency != null) { - return; - } - sDependency = new Dependency(); - rootComponent.createDependency().createSystemUI(sDependency); - sDependency.start(); - } - protected void start() { // TODO: Think about ways to push these creation rules out of Dependency to cut down // on imports. @@ -508,6 +500,7 @@ public class Dependency { 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 d46a86cc728f..239cbfe38975 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.MAIN_LOOPER_NAME; 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 @@ public class DependencyProvider { @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/ForegroundServicesDialog.java b/packages/SystemUI/src/com/android/systemui/ForegroundServicesDialog.java index 6fec92c84fec..710980a274a2 100644 --- a/packages/SystemUI/src/com/android/systemui/ForegroundServicesDialog.java +++ b/packages/SystemUI/src/com/android/systemui/ForegroundServicesDialog.java @@ -44,6 +44,8 @@ import com.android.internal.logging.nano.MetricsProto; import java.util.ArrayList; +import javax.inject.Inject; + /** * Show a list of currently running foreground services (supplied by the caller) * that the user can tap through to their application details. @@ -72,10 +74,14 @@ public final class ForegroundServicesDialog extends AlertActivity implements } }; + @Inject + ForegroundServicesDialog() { + super(); + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - Dependency.initDependencies(SystemUIFactory.getInstance().getRootComponent()); mMetricsLogger = Dependency.get(MetricsLogger.class); diff --git a/packages/SystemUI/src/com/android/systemui/ServiceBinder.java b/packages/SystemUI/src/com/android/systemui/ServiceBinder.java index 131a0f8c81a9..e761a2be0b0f 100644 --- a/packages/SystemUI/src/com/android/systemui/ServiceBinder.java +++ b/packages/SystemUI/src/com/android/systemui/ServiceBinder.java @@ -26,15 +26,10 @@ import dagger.multibindings.ClassKey; import dagger.multibindings.IntoMap; /** - * Services and Activities that are injectable should go here. + * Services that are injectable should go here. */ @Module public abstract class ServiceBinder { - - @Binds - public abstract ContextComponentHelper bindComponentHelper( - ContextComponentResolver componentHelper); - @Binds @IntoMap @ClassKey(DozeService.class) diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java index 3eb330e04381..2c8324cafca0 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java @@ -16,6 +16,7 @@ package com.android.systemui; +import android.app.Activity; import android.app.Application; import android.app.Service; import android.content.ContentProvider; @@ -23,6 +24,7 @@ import android.content.Context; import android.content.Intent; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.core.app.AppComponentFactory; import javax.inject.Inject; @@ -87,6 +89,18 @@ public class SystemUIAppComponentFactory extends AppComponentFactory { @NonNull @Override + public Activity instantiateActivityCompat(@NonNull ClassLoader cl, @NonNull String className, + @Nullable Intent intent) + throws InstantiationException, IllegalAccessException, ClassNotFoundException { + Activity activity = mComponentHelper.resolveActivity(className); + if (activity != null) { + return activity; + } + return super.instantiateActivityCompat(cl, className, intent); + } + + @NonNull + @Override public Service instantiateServiceCompat( @NonNull ClassLoader cl, @NonNull String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java index 88d659039b4b..4531c892a022 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java @@ -24,7 +24,7 @@ import dagger.multibindings.ClassKey; import dagger.multibindings.IntoMap; /** - * Services and Activities that are injectable should go here. + * SystemUI objects that are injectable should go here. */ @Module public abstract class SystemUIBinder { diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index cd16d85685de..8e693185ab7f 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -102,7 +102,10 @@ public class SystemUIFactory { // Every other part of our codebase currently relies on Dependency, so we // really need to ensure the Dependency gets initialized early on. - Dependency.initDependencies(mRootComponent); + + Dependency dependency = new Dependency(); + mRootComponent.createDependency().createSystemUI(dependency); + dependency.start(); } protected void initWithRootComponent(@NonNull SystemUIRootComponent rootComponent) { diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/SystemUIModule.java index ff4eb83bd796..b0316e22de06 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.assist.AssistModule; 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; @@ -35,7 +35,7 @@ import dagger.Provides; * A dagger module for injecting components of System UI that are not overridden by the System UI * implementation. */ -@Module(includes = {AssistModule.class}) +@Module(includes = {AssistModule.class, ComponentBinder.class}) public abstract class SystemUIModule { @Singleton diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIRootComponent.java b/packages/SystemUI/src/com/android/systemui/SystemUIRootComponent.java index 6ce673b0e9b6..c70b2fc3292a 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIRootComponent.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIRootComponent.java @@ -37,8 +37,6 @@ import dagger.Component; @Component(modules = { DependencyProvider.class, DependencyBinder.class, - ServiceBinder.class, - SystemUIBinder.class, SystemUIFactory.ContextHolder.class, SystemUIModule.class, SystemUIDefaultModule.class}) diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java index 1c5e80005d84..8f1fcae8e0f7 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java @@ -84,7 +84,7 @@ public class SystemUIService extends Service { } } 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/assist/AssistHandleLikeHomeBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java index a0d8b4c58f1a..ccca447ad842 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java @@ -23,6 +23,7 @@ import androidx.annotation.Nullable; import com.android.systemui.assist.AssistHandleBehaviorController.BehaviorController; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.model.SysUiState; +import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shared.system.QuickStepContract; import java.io.PrintWriter; @@ -39,44 +40,55 @@ import dagger.Lazy; @Singleton final class AssistHandleLikeHomeBehavior implements BehaviorController { + private final StatusBarStateController.StateListener mStatusBarStateListener = + new StatusBarStateController.StateListener() { + @Override + public void onDozingChanged(boolean isDozing) { + handleDozingChanged(isDozing); + } + }; private final WakefulnessLifecycle.Observer mWakefulnessLifecycleObserver = new WakefulnessLifecycle.Observer() { @Override public void onStartedWakingUp() { - handleDozingChanged(/* isDozing = */ true); + handleWakefullnessChanged(/* isAwake = */ false); } @Override public void onFinishedWakingUp() { - handleDozingChanged(/* isDozing = */ false); + handleWakefullnessChanged(/* isAwake = */ true); } @Override public void onStartedGoingToSleep() { - handleDozingChanged(/* isDozing = */ true); + handleWakefullnessChanged(/* isAwake = */ false); } @Override public void onFinishedGoingToSleep() { - handleDozingChanged(/* isDozing = */ true); + handleWakefullnessChanged(/* isAwake = */ false); } }; private final SysUiState.SysUiStateCallback mSysUiStateCallback = this::handleSystemUiStateChange; + private final Lazy<StatusBarStateController> mStatusBarStateController; private final Lazy<WakefulnessLifecycle> mWakefulnessLifecycle; private final Lazy<SysUiState> mSysUiFlagContainer; private boolean mIsDozing; + private boolean mIsAwake; private boolean mIsHomeHandleHiding; @Nullable private AssistHandleCallbacks mAssistHandleCallbacks; @Inject AssistHandleLikeHomeBehavior( + Lazy<StatusBarStateController> statusBarStateController, Lazy<WakefulnessLifecycle> wakefulnessLifecycle, Lazy<SysUiState> sysUiFlagContainer) { + mStatusBarStateController = statusBarStateController; mWakefulnessLifecycle = wakefulnessLifecycle; mSysUiFlagContainer = sysUiFlagContainer; } @@ -84,8 +96,10 @@ final class AssistHandleLikeHomeBehavior implements BehaviorController { @Override public void onModeActivated(Context context, AssistHandleCallbacks callbacks) { mAssistHandleCallbacks = callbacks; - mIsDozing = mWakefulnessLifecycle.get().getWakefulness() - != WakefulnessLifecycle.WAKEFULNESS_AWAKE; + mIsDozing = mStatusBarStateController.get().isDozing(); + mStatusBarStateController.get().addCallback(mStatusBarStateListener); + mIsAwake = mWakefulnessLifecycle.get().getWakefulness() + == WakefulnessLifecycle.WAKEFULNESS_AWAKE; mWakefulnessLifecycle.get().addObserver(mWakefulnessLifecycleObserver); mSysUiFlagContainer.get().addCallback(mSysUiStateCallback); callbackForCurrentState(); @@ -94,6 +108,7 @@ final class AssistHandleLikeHomeBehavior implements BehaviorController { @Override public void onModeDeactivated() { mAssistHandleCallbacks = null; + mStatusBarStateController.get().removeCallback(mStatusBarStateListener); mWakefulnessLifecycle.get().removeObserver(mWakefulnessLifecycleObserver); mSysUiFlagContainer.get().removeCallback(mSysUiStateCallback); } @@ -111,6 +126,15 @@ final class AssistHandleLikeHomeBehavior implements BehaviorController { callbackForCurrentState(); } + private void handleWakefullnessChanged(boolean isAwake) { + if (mIsAwake == isAwake) { + return; + } + + mIsAwake = isAwake; + callbackForCurrentState(); + } + private void handleSystemUiStateChange(int sysuiStateFlags) { boolean isHomeHandleHiding = isHomeHandleHiding(sysuiStateFlags); if (mIsHomeHandleHiding == isHomeHandleHiding) { @@ -126,18 +150,23 @@ final class AssistHandleLikeHomeBehavior implements BehaviorController { return; } - if (mIsHomeHandleHiding || mIsDozing) { + if (mIsHomeHandleHiding || !isFullyAwake()) { mAssistHandleCallbacks.hide(); } else { mAssistHandleCallbacks.showAndStay(); } } + private boolean isFullyAwake() { + return mIsAwake && !mIsDozing; + } + @Override public void dump(PrintWriter pw, String prefix) { - pw.println("Current AssistHandleLikeHomeBehavior State:"); + pw.println(prefix + "Current AssistHandleLikeHomeBehavior State:"); pw.println(prefix + " mIsDozing=" + mIsDozing); + pw.println(prefix + " mIsAwake=" + mIsAwake); pw.println(prefix + " mIsHomeHandleHiding=" + mIsHomeHandleHiding); } } diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java index d371dbead42b..3ff1b97c3753 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java @@ -94,6 +94,11 @@ final class AssistHandleReminderExpBehavior implements BehaviorController { public void onStateChanged(int newState) { handleStatusBarStateChanged(newState); } + + @Override + public void onDozingChanged(boolean isDozing) { + handleDozingChanged(isDozing); + } }; private final TaskStackChangeListener mTaskStackChangeListener = new TaskStackChangeListener() { @@ -119,15 +124,25 @@ final class AssistHandleReminderExpBehavior implements BehaviorController { private final WakefulnessLifecycle.Observer mWakefulnessLifecycleObserver = new WakefulnessLifecycle.Observer() { @Override + public void onStartedWakingUp() { + handleWakefullnessChanged(/* isAwake = */ false); + } + + @Override public void onFinishedWakingUp() { - handleDozingChanged(false); + handleWakefullnessChanged(/* isAwake = */ true); } @Override public void onStartedGoingToSleep() { - handleDozingChanged(true); + handleWakefullnessChanged(/* isAwake = */ false); } - }; + + @Override + public void onFinishedGoingToSleep() { + handleWakefullnessChanged(/* isAwake = */ false); + } + }; private final BroadcastReceiver mDefaultHomeBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -149,6 +164,7 @@ final class AssistHandleReminderExpBehavior implements BehaviorController { private boolean mOnLockscreen; private boolean mIsDozing; + private boolean mIsAwake; private int mRunningTaskId; private boolean mIsNavBarHidden; private boolean mIsLauncherShowing; @@ -201,6 +217,7 @@ final class AssistHandleReminderExpBehavior implements BehaviorController { mDefaultHome = getCurrentDefaultHome(); context.registerReceiver(mDefaultHomeBroadcastReceiver, mDefaultHomeIntentFilter); mOnLockscreen = onLockscreen(mStatusBarStateController.get().getState()); + mIsDozing = mStatusBarStateController.get().isDozing(); mStatusBarStateController.get().addCallback(mStatusBarStateListener); ActivityManager.RunningTaskInfo runningTaskInfo = mActivityManagerWrapper.get().getRunningTask(); @@ -208,8 +225,8 @@ final class AssistHandleReminderExpBehavior implements BehaviorController { mActivityManagerWrapper.get().registerTaskStackListener(mTaskStackChangeListener); mOverviewProxyService.get().addCallback(mOverviewProxyListener); mSysUiFlagContainer.get().addCallback(mSysUiStateCallback); - mIsDozing = mWakefulnessLifecycle.get().getWakefulness() - != WakefulnessLifecycle.WAKEFULNESS_AWAKE; + mIsAwake = mWakefulnessLifecycle.get().getWakefulness() + == WakefulnessLifecycle.WAKEFULNESS_AWAKE; mWakefulnessLifecycle.get().addObserver(mWakefulnessLifecycleObserver); mLearningTimeElapsed = Settings.Secure.getLong( @@ -252,7 +269,10 @@ final class AssistHandleReminderExpBehavior implements BehaviorController { @Override public void onAssistHandlesRequested() { - if (mAssistHandleCallbacks != null && !mIsDozing && !mIsNavBarHidden && !mOnLockscreen) { + if (mAssistHandleCallbacks != null + && isFullyAwake() + && !mIsNavBarHidden + && !mOnLockscreen) { mAssistHandleCallbacks.showAndGo(); } } @@ -299,6 +319,16 @@ final class AssistHandleReminderExpBehavior implements BehaviorController { callbackForCurrentState(/* justUnlocked = */ false); } + private void handleWakefullnessChanged(boolean isAwake) { + if (mIsAwake == isAwake) { + return; + } + + resetConsecutiveTaskSwitches(); + mIsAwake = isAwake; + callbackForCurrentState(/* justUnlocked = */ false); + } + private void handleTaskStackTopChanged(int taskId, @Nullable ComponentName taskComponentName) { if (mRunningTaskId == taskId || taskComponentName == null) { return; @@ -352,7 +382,7 @@ final class AssistHandleReminderExpBehavior implements BehaviorController { return; } - if (mIsDozing || mIsNavBarHidden || mOnLockscreen || !getShowWhenTaught()) { + if (!isFullyAwake() || mIsNavBarHidden || mOnLockscreen || !getShowWhenTaught()) { mAssistHandleCallbacks.hide(); } else if (justUnlocked) { long currentEpochDay = LocalDate.now().toEpochDay(); @@ -374,7 +404,7 @@ final class AssistHandleReminderExpBehavior implements BehaviorController { return; } - if (mIsDozing || mIsNavBarHidden || isSuppressed()) { + if (!isFullyAwake() || mIsNavBarHidden || isSuppressed()) { mAssistHandleCallbacks.hide(); } else if (mOnLockscreen) { mAssistHandleCallbacks.showAndStay(); @@ -425,6 +455,10 @@ final class AssistHandleReminderExpBehavior implements BehaviorController { mHandler.postDelayed(mResetConsecutiveTaskSwitches, getShowAndGoDelayResetTimeoutMs()); } + private boolean isFullyAwake() { + return mIsAwake && !mIsDozing; + } + private long getLearningTimeMs() { return mPhenotypeHelper.getLong( SystemUiDeviceConfigFlags.ASSIST_HANDLES_LEARN_TIME_MS, @@ -484,6 +518,7 @@ final class AssistHandleReminderExpBehavior implements BehaviorController { pw.println(prefix + "Current AssistHandleReminderExpBehavior State:"); pw.println(prefix + " mOnLockscreen=" + mOnLockscreen); pw.println(prefix + " mIsDozing=" + mIsDozing); + pw.println(prefix + " mIsAwake=" + mIsAwake); pw.println(prefix + " mRunningTaskId=" + mRunningTaskId); pw.println(prefix + " mDefaultHome=" + mDefaultHome); pw.println(prefix + " mIsNavBarHidden=" + mIsNavBarHidden); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java index e4d2005c8867..73bbce9c5b35 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 @@ public abstract class AuthBiometricView extends LinearLayout { 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 67fc3e32883f..72ada6e90cd0 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -48,12 +48,10 @@ import android.app.NotificationManager; 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; import android.os.ServiceManager; -import android.provider.Settings; import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.ZenModeConfig; import android.util.ArraySet; @@ -61,8 +59,6 @@ import android.util.Log; 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; @@ -76,6 +72,7 @@ import com.android.systemui.Dependency; 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; @@ -131,9 +128,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi public static final int MAX_BUBBLES = 5; // TODO: actually enforce this - /** Flag to enable or disable the entire feature */ - private static final String ENABLE_BUBBLES = "experiment_enable_bubbles"; - private final Context mContext; private final NotificationEntryManager mNotificationEntryManager; private final BubbleTaskStackListener mTaskStackListener; @@ -560,7 +554,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi // Need to check for !appCancel here because the notification may have // previously been dismissed & entry.isRowDismissed would still be true - boolean userRemovedNotif = (entry.isRowDismissed() && !isAppCancel) + boolean userRemovedNotif = (entry != null && entry.isRowDismissed() && !isAppCancel) || isClearAll || isUserDimiss || isSummaryCancel; if (isSummaryOfBubbles) { @@ -570,7 +564,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi // The bubble notification sticks around in the data as long as the bubble is // not dismissed and the app hasn't cancelled the notification. Bubble bubble = mBubbleData.getBubbleWithKey(key); - boolean bubbleExtended = entry.isBubble() && userRemovedNotif; + boolean bubbleExtended = entry != null && entry.isBubble() && userRemovedNotif; if (bubbleExtended) { bubble.setShowInShadeWhenBubble(false); bubble.setShowBubbleDot(false); @@ -579,7 +573,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } mNotificationEntryManager.updateNotifications(); return true; - } else if (!userRemovedNotif) { + } else if (!userRemovedNotif && entry != null) { // This wasn't a user removal so we should remove the bubble as well mBubbleData.notificationEntryRemoved(entry, DISMISS_NOTIF_CANCEL); return false; @@ -638,9 +632,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi private final NotificationEntryListener mEntryListener = new NotificationEntryListener() { @Override public void onPendingEntryAdded(NotificationEntry entry) { - if (!areBubblesEnabled(mContext)) { - return; - } if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry) && canLaunchInActivityView(mContext, entry)) { updateBubble(entry); @@ -649,9 +640,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi @Override public void onPreEntryUpdated(NotificationEntry entry) { - if (!areBubblesEnabled(mContext)) { - return; - } boolean shouldBubble = mNotificationInterruptionStateProvider.shouldBubbleUp(entry) && canLaunchInActivityView(mContext, entry); if (!shouldBubble && mBubbleData.hasBubbleWithKey(entry.key)) { @@ -926,7 +914,9 @@ public class BubbleController implements ConfigurationController.ConfigurationLi @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); } @@ -934,22 +924,17 @@ public class BubbleController implements ConfigurationController.ConfigurationLi @Override public void onSingleTaskDisplayEmpty(int displayId) { - final Bubble expandedBubble = getExpandedBubble(mContext); - if (expandedBubble == null) { - return; - } - if (expandedBubble.getDisplayId() == displayId) { + final Bubble expandedBubble = mStackView != null + ? mStackView.getExpandedBubble() + : null; + int expandedId = expandedBubble != null ? expandedBubble.getDisplayId() : -1; + if (mStackView != null && mStackView.isExpanded() && expandedId == displayId) { mBubbleData.setExpanded(false); - expandedBubble.getExpandedView().notifyDisplayEmpty(); } + mBubbleData.notifyDisplayEmpty(displayId); } } - private static boolean areBubblesEnabled(Context context) { - return Settings.Secure.getInt(context.getContentResolver(), - ENABLE_BUBBLES, 1) != 0; - } - /** * Whether an intent is properly configured to display in an {@link android.app.ActivityView}. * @@ -993,32 +978,12 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } /** PinnedStackListener that dispatches IME visibility updates to the stack. */ - 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 {} - + private class BubblesImeListener extends PinnedStackListenerForwarder.PinnedStackListener { @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 81c8da8247ec..d43e030ed9eb 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 @@ public class BubbleData { 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 @@ public class BubbleData { } else { // Updates an existing bubble bubble.updateEntry(entry); + bubble.setSuppressFlyout(suppressFlyout); doUpdate(bubble); } + if (bubble.shouldAutoExpand()) { setSelectedBubbleInternal(bubble); if (!mExpanded) { @@ -392,6 +396,22 @@ public class BubbleData { dispatchPendingChanges(); } + /** + * Indicates that the provided display is no longer in use and should be cleaned up. + * + * @param displayId the id of the display to clean up. + */ + void notifyDisplayEmpty(int displayId) { + for (Bubble b : mBubbles) { + if (b.getDisplayId() == displayId) { + if (b.getExpandedView() != null) { + b.getExpandedView().notifyDisplayEmpty(); + } + return; + } + } + } + private void dispatchPendingChanges() { if (mListener != null && mStateChange.anythingChanged()) { mListener.applyUpdate(mStateChange); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java index be10dc565159..521ebde7d2f0 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java @@ -105,6 +105,7 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList private Drawable mAppIcon; private BubbleController mBubbleController = Dependency.get(BubbleController.class); + private WindowManager mWindowManager; private BubbleStackView mStackView; @@ -202,9 +203,9 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList super(context, attrs, defStyleAttr, defStyleRes); mPm = context.getPackageManager(); mDisplaySize = new Point(); - WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); // Get the real size -- this includes screen decorations (notches, statusbar, navbar). - wm.getDefaultDisplay().getRealSize(mDisplaySize); + mWindowManager.getDefaultDisplay().getRealSize(mDisplaySize); Resources res = getResources(); mMinHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height); mPointerMargin = res.getDimensionPixelSize(R.dimen.bubble_pointer_margin); @@ -326,7 +327,10 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList if (usingActivityView()) { int[] screenLoc = mActivityView.getLocationOnScreen(); final int activityViewBottom = screenLoc[1] + mActivityView.getHeight(); - final int keyboardTop = mDisplaySize.y - insets.getSystemWindowInsetBottom(); + final int keyboardTop = mDisplaySize.y - Math.max(insets.getSystemWindowInsetBottom(), + insets.getDisplayCutout() != null + ? insets.getDisplayCutout().getSafeInsetBottom() + : 0); final int insetsBottom = Math.max(activityViewBottom - keyboardTop, 0); mActivityView.setForwardedInsets(Insets.of(0, 0, 0, insetsBottom)); } @@ -444,6 +448,7 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList } private int getMaxExpandedHeight() { + mWindowManager.getDefaultDisplay().getRealSize(mDisplaySize); int[] windowLocation = mActivityView.getLocationOnScreen(); int bottomInset = getRootWindowInsets() != null ? getRootWindowInsets().getStableInsetBottom() diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index cc250b46c040..31cf853dce04 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -45,6 +45,7 @@ import android.service.notification.StatusBarNotification; import android.util.Log; import android.util.StatsLog; import android.view.Choreographer; +import android.view.DisplayCutout; import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -340,7 +341,8 @@ public class BubbleStackView extends FrameLayout { mDisplaySize = new Point(); WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - wm.getDefaultDisplay().getSize(mDisplaySize); + // We use the real size & subtract screen decorations / window insets ourselves when needed + wm.getDefaultDisplay().getRealSize(mDisplaySize); mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); @@ -417,7 +419,35 @@ public class BubbleStackView extends FrameLayout { mOrientationChangedListener = (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { - mExpandedAnimationController.updateOrientation(mOrientation); + mExpandedAnimationController.updateOrientation(mOrientation, mDisplaySize); + mStackAnimationController.updateOrientation(mOrientation); + + // Reposition & adjust the height for new orientation + if (mIsExpanded) { + mExpandedViewContainer.setTranslationY(getExpandedViewY()); + mExpandedBubble.getExpandedView().updateView(); + } + + // Need to update the padding around the view + WindowInsets insets = getRootWindowInsets(); + int leftPadding = mExpandedViewPadding; + int rightPadding = mExpandedViewPadding; + if (insets != null) { + // Can't have the expanded view overlaying notches + int cutoutLeft = 0; + int cutoutRight = 0; + DisplayCutout cutout = insets.getDisplayCutout(); + if (cutout != null) { + cutoutLeft = cutout.getSafeInsetLeft(); + cutoutRight = cutout.getSafeInsetRight(); + } + // Or overlaying nav or status bar + leftPadding += Math.max(cutoutLeft, insets.getStableInsetLeft()); + rightPadding += Math.max(cutoutRight, insets.getStableInsetRight()); + } + mExpandedViewContainer.setPadding(leftPadding, mExpandedViewPadding, + rightPadding, mExpandedViewPadding); + if (mIsExpanded) { // Re-draw bubble row and pointer for new orientation. mExpandedAnimationController.expandFromStack(() -> { @@ -488,6 +518,11 @@ public class BubbleStackView extends FrameLayout { public void onOrientationChanged(int orientation) { mOrientation = orientation; + // Display size is based on the rotation device was in when requested, we should update it + // We use the real size & subtract screen decorations / window insets ourselves when needed + WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); + wm.getDefaultDisplay().getRealSize(mDisplaySize); + // Some resources change depending on orientation Resources res = getContext().getResources(); mStatusBarHeight = res.getDimensionPixelSize( @@ -1584,11 +1619,9 @@ public class BubbleStackView extends FrameLayout { int index = getBubbleIndex(expandedBubble); float bubbleLeftFromScreenLeft = mExpandedAnimationController.getBubbleLeft(index); float halfBubble = mBubbleSize / 2f; - - // Bubbles live in expanded view container (x includes expanded view padding). - // Pointer lives in expanded view, which has padding (x does not include padding). - // Remove padding when deriving pointer location from bubbles. - float bubbleCenter = bubbleLeftFromScreenLeft + halfBubble - mExpandedViewPadding; + float bubbleCenter = bubbleLeftFromScreenLeft + halfBubble; + // Padding might be adjusted for insets, so get it directly from the view + bubbleCenter -= mExpandedViewContainer.getPaddingLeft(); expandedBubble.getExpandedView().setPointerPosition(bubbleCenter); } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java index c332d15a9b47..59d68bca93c3 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java @@ -21,6 +21,7 @@ import android.content.res.Resources; import android.graphics.Path; import android.graphics.Point; import android.graphics.PointF; +import android.view.DisplayCutout; import android.view.View; import android.view.WindowInsets; @@ -57,6 +58,9 @@ public class ExpandedAnimationController /** Stiffness for the expand/collapse path-following animation. */ private static final int EXPAND_COLLAPSE_ANIM_STIFFNESS = 1000; + /** What percentage of the screen to use when centering the bubbles in landscape. */ + private static final float CENTER_BUBBLES_LANDSCAPE_PERCENT = 0.66f; + /** Horizontal offset between bubbles, which we need to know to re-stack them. */ private float mStackOffsetPx; /** Space between status bar and bubbles in the expanded state. */ @@ -69,8 +73,8 @@ public class ExpandedAnimationController private Point mDisplaySize; /** Max number of bubbles shown in row above expanded view.*/ private int mBubblesMaxRendered; - /** Width of current screen orientation. */ - private float mScreenWidth; + /** What the current screen orientation is. */ + private int mScreenOrientation; /** Whether the dragged-out bubble is in the dismiss target. */ private boolean mIndividualBubbleWithinDismissTarget = false; @@ -97,8 +101,7 @@ public class ExpandedAnimationController public ExpandedAnimationController(Point displaySize, int expandedViewPadding, int orientation) { - mDisplaySize = displaySize; - updateOrientation(orientation); + updateOrientation(orientation, displaySize); mExpandedViewPadding = expandedViewPadding; mLauncherGridDiff = 30f; } @@ -136,18 +139,16 @@ public class ExpandedAnimationController /** * Update effective screen width based on current orientation. * @param orientation Landscape or portrait. + * @param displaySize Updated display size. */ - public void updateOrientation(int orientation) { - if (orientation == Configuration.ORIENTATION_LANDSCAPE) { - mScreenWidth = mDisplaySize.y; - } else { - mScreenWidth = mDisplaySize.x; - } + public void updateOrientation(int orientation, Point displaySize) { + mScreenOrientation = orientation; + mDisplaySize = displaySize; if (mLayout != null) { Resources res = mLayout.getContext().getResources(); + mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top); mStatusBarHeight = res.getDimensionPixelSize( com.android.internal.R.dimen.status_bar_height); - mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top); } } @@ -499,6 +500,50 @@ public class ExpandedAnimationController return getRowLeft() + bubbleFromRowLeft; } + /** + * When expanded, the bubbles are centered in the screen. In portrait, all available space is + * used. In landscape we have too much space so the value is restricted. This method accounts + * for window decorations (nav bar, cutouts). + * + * @return the desired width to display the expanded bubbles in. + */ + private float getWidthForDisplayingBubbles() { + final float availableWidth = getAvailableScreenWidth(true /* includeStableInsets */); + if (mScreenOrientation == Configuration.ORIENTATION_LANDSCAPE) { + // display size y in landscape will be the smaller dimension of the screen + return Math.max(mDisplaySize.y, availableWidth * CENTER_BUBBLES_LANDSCAPE_PERCENT); + } else { + return availableWidth; + } + } + + /** + * Determines the available screen width without the cutout. + * + * @param subtractStableInsets Whether or not stable insets should also be removed from the + * returned width. + * @return the total screen width available accounting for cutouts and insets, + * iff {@param includeStableInsets} is true. + */ + private float getAvailableScreenWidth(boolean subtractStableInsets) { + float availableSize = mDisplaySize.x; + WindowInsets insets = mLayout != null ? mLayout.getRootWindowInsets() : null; + if (insets != null) { + int cutoutLeft = 0; + int cutoutRight = 0; + DisplayCutout cutout = insets.getDisplayCutout(); + if (cutout != null) { + cutoutLeft = cutout.getSafeInsetLeft(); + cutoutRight = cutout.getSafeInsetRight(); + } + final int stableLeft = subtractStableInsets ? insets.getStableInsetLeft() : 0; + final int stableRight = subtractStableInsets ? insets.getStableInsetRight() : 0; + availableSize -= Math.max(stableLeft, cutoutLeft); + availableSize -= Math.max(stableRight, cutoutRight); + } + return availableSize; + } + private float getRowLeft() { if (mLayout == null) { return 0; @@ -510,9 +555,12 @@ public class ExpandedAnimationController final float totalGapWidth = (bubbleCount - 1) * getSpaceBetweenBubbles(); final float rowWidth = totalGapWidth + totalBubbleWidth; - final float centerScreen = mScreenWidth / 2f; + // This display size we're using includes the size of the insets, we want the true + // center of the display minus the notch here, which means we should include the + // stable insets (e.g. status bar, nav bar) in this calculation. + final float trueCenter = getAvailableScreenWidth(false /* subtractStableInsets */) / 2f; final float halfRow = rowWidth / 2f; - final float rowLeft = centerScreen - halfRow; + final float rowLeft = trueCenter - halfRow; return rowLeft; } @@ -530,7 +578,7 @@ public class ExpandedAnimationController * Launcher's app icon grid edge that we must match */ final float rowMargins = (mExpandedViewPadding + mLauncherGridDiff) * 2; - final float maxRowWidth = mScreenWidth - rowMargins; + final float maxRowWidth = getWidthForDisplayingBubbles() - rowMargins; final float totalBubbleWidth = mBubblesMaxRendered * mBubbleSizePx; final float totalGapWidth = maxRowWidth - totalBubbleWidth; diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java index c2b0fe4ab2d9..20742d6d2358 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.FalsingManager; 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 85bc22bab36f..914258f48b46 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.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHT 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.FalsingPlugin; 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 @@ public class FalsingManagerProxy implements FalsingManager { 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 3f5cae678d01..bd0906a63370 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java @@ -30,11 +30,12 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; 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; import java.util.List; +import java.util.Locale; /** * FalsingManager designed to make clear why a touch was rejected. @@ -42,7 +43,7 @@ import java.util.List; public class BrightLineFalsingManager implements FalsingManager { static final boolean DEBUG = false; - private static final String TAG = "FalsingManagerPlugin"; + private static final String TAG = "FalsingManager"; private final FalsingDataProvider mDataProvider; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; @@ -142,7 +143,15 @@ public class BrightLineFalsingManager implements FalsingManager { boolean r = !mJustUnlockedWithFace && mClassifiers.stream().anyMatch(falsingClassifier -> { boolean result = falsingClassifier.isFalseTouch(); if (result) { - logInfo(falsingClassifier.getClass().getName() + ": true"); + logInfo(String.format( + (Locale) null, + "{classifier=%s, interactionType=%d}", + falsingClassifier.getClass().getName(), + mDataProvider.getInteractionType())); + String reason = falsingClassifier.getReason(); + if (reason != null) { + logInfo(reason); + } } else { logDebug(falsingClassifier.getClass().getName() + ": false"); } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/DiagonalClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/DiagonalClassifier.java index 9c03b915000e..520e0a12dec4 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/DiagonalClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/DiagonalClassifier.java @@ -25,6 +25,8 @@ import android.provider.DeviceConfig; import com.android.systemui.util.DeviceConfigProxy; +import java.util.Locale; + /** * False on swipes that are too close to 45 degrees. * @@ -84,6 +86,15 @@ class DiagonalClassifier extends FalsingClassifier { maxAngle + ONE_HUNDRED_EIGHTY_DEG); } + @Override + String getReason() { + return String.format( + (Locale) null, + "{angle=%f, vertical=%s}", + getAngle(), + isVertical()); + } + private boolean angleBetween(float angle, float min, float max) { // No need to normalize angle as it is guaranteed to be between 0 and 2*PI. min = normalizeAngle(min); diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/DistanceClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/DistanceClassifier.java index 0954f9e6e672..7f45cc8f4c56 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/DistanceClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/DistanceClassifier.java @@ -30,6 +30,7 @@ import android.view.VelocityTracker; import com.android.systemui.util.DeviceConfigProxy; import java.util.List; +import java.util.Locale; /** * Ensure that the swipe + momentum covers a minimum distance. @@ -144,15 +145,66 @@ class DistanceClassifier extends FalsingClassifier { @Override public boolean isFalseTouch() { - return !getDistances().getPassedFlingThreshold(); + return !getPassedFlingThreshold(); + } + + @Override + String getReason() { + DistanceVectors distanceVectors = getDistances(); + + return String.format( + (Locale) null, + "{distanceVectors=%s, isHorizontal=%s, velocityToDistanceMultiplier=%f, " + + "horizontalFlingThreshold=%f, verticalFlingThreshold=%f, " + + "horizontalSwipeThreshold=%f, verticalSwipeThreshold=%s}", + distanceVectors, + isHorizontal(), + mVelocityToDistanceMultiplier, + mHorizontalFlingThresholdPx, + mVerticalFlingThresholdPx, + mHorizontalSwipeThresholdPx, + mVerticalSwipeThresholdPx); } boolean isLongSwipe() { - boolean longSwipe = getDistances().getPassedDistanceThreshold(); + boolean longSwipe = getPassedDistanceThreshold(); logDebug("Is longSwipe? " + longSwipe); return longSwipe; } + private boolean getPassedDistanceThreshold() { + DistanceVectors distanceVectors = getDistances(); + if (isHorizontal()) { + logDebug("Horizontal swipe distance: " + Math.abs(distanceVectors.mDx)); + logDebug("Threshold: " + mHorizontalSwipeThresholdPx); + + return Math.abs(distanceVectors.mDx) >= mHorizontalSwipeThresholdPx; + } + + logDebug("Vertical swipe distance: " + Math.abs(distanceVectors.mDy)); + logDebug("Threshold: " + mVerticalSwipeThresholdPx); + return Math.abs(distanceVectors.mDy) >= mVerticalSwipeThresholdPx; + } + + private boolean getPassedFlingThreshold() { + DistanceVectors distanceVectors = getDistances(); + + float dX = distanceVectors.mDx + distanceVectors.mVx * mVelocityToDistanceMultiplier; + float dY = distanceVectors.mDy + distanceVectors.mVy * mVelocityToDistanceMultiplier; + + if (isHorizontal()) { + logDebug("Horizontal swipe and fling distance: " + distanceVectors.mDx + ", " + + distanceVectors.mVx * mVelocityToDistanceMultiplier); + logDebug("Threshold: " + mHorizontalFlingThresholdPx); + return Math.abs(dX) >= mHorizontalFlingThresholdPx; + } + + logDebug("Vertical swipe and fling distance: " + distanceVectors.mDy + ", " + + distanceVectors.mVy * mVelocityToDistanceMultiplier); + logDebug("Threshold: " + mVerticalFlingThresholdPx); + return Math.abs(dY) >= mVerticalFlingThresholdPx; + } + private class DistanceVectors { final float mDx; final float mDy; @@ -166,34 +218,9 @@ class DistanceClassifier extends FalsingClassifier { this.mVy = vY; } - boolean getPassedDistanceThreshold() { - if (isHorizontal()) { - logDebug("Horizontal swipe distance: " + Math.abs(mDx)); - logDebug("Threshold: " + mHorizontalSwipeThresholdPx); - - return Math.abs(mDx) >= mHorizontalSwipeThresholdPx; - } - - logDebug("Vertical swipe distance: " + Math.abs(mDy)); - logDebug("Threshold: " + mVerticalSwipeThresholdPx); - return Math.abs(mDy) >= mVerticalSwipeThresholdPx; - } - - boolean getPassedFlingThreshold() { - float dX = this.mDx + this.mVx * mVelocityToDistanceMultiplier; - float dY = this.mDy + this.mVy * mVelocityToDistanceMultiplier; - - if (isHorizontal()) { - logDebug("Horizontal swipe and fling distance: " + this.mDx + ", " - + this.mVx * mVelocityToDistanceMultiplier); - logDebug("Threshold: " + mHorizontalFlingThresholdPx); - return Math.abs(dX) >= mHorizontalFlingThresholdPx; - } - - logDebug("Vertical swipe and fling distance: " + this.mDy + ", " - + this.mVy * mVelocityToDistanceMultiplier); - logDebug("Threshold: " + mVerticalFlingThresholdPx); - return Math.abs(dY) >= mVerticalFlingThresholdPx; + @Override + public String toString() { + return String.format((Locale) null, "{dx=%f, vx=%f, dy=%f, vy=%f}", mDx, mVx, mDy, mVy); } } } 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 bf397518de46..7555051fac48 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 @@ package com.android.systemui.classifier.brightline; 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; @@ -117,6 +117,14 @@ abstract class FalsingClassifier { */ abstract boolean isFalseTouch(); + /** + * Give the classifier a chance to log more details about why it triggered. + * + * This should only be called after a call to {@link #isFalseTouch()}, and only if + * {@link #isFalseTouch()} returns true; + */ + abstract String getReason(); + static void logDebug(String msg) { BrightLineFalsingManager.logDebug(msg); } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/PointerCountClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/PointerCountClassifier.java index 40e141fbf988..85a4d234b5df 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/PointerCountClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/PointerCountClassifier.java @@ -18,6 +18,8 @@ package com.android.systemui.classifier.brightline; import android.view.MotionEvent; +import java.util.Locale; + /** * False touch if more than one finger touches the screen. * @@ -50,4 +52,13 @@ class PointerCountClassifier extends FalsingClassifier { public boolean isFalseTouch() { return mMaxPointerCount > MAX_ALLOWED_POINTERS; } + + @Override + String getReason() { + return String.format( + (Locale) null, + "{pointersObserved=%d, threshold=%d}", + mMaxPointerCount, + MAX_ALLOWED_POINTERS); + } } 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 eeca409866a8..749914e1b625 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,9 @@ import android.provider.DeviceConfig; import android.view.MotionEvent; import com.android.systemui.util.DeviceConfigProxy; -import com.android.systemui.util.ProximitySensor; +import com.android.systemui.util.sensors.ProximitySensor; + +import java.util.Locale; /** @@ -121,6 +123,16 @@ class ProximityClassifier extends FalsingClassifier { return false; } + @Override + String getReason() { + return String.format( + (Locale) null, + "{percentInProximity=%f, threshold=%f, distanceClassifier=%s}", + mPercentNear, + mPercentCoveredThreshold, + mDistanceClassifier.getReason()); + } + /** * @param near is the sensor showing the near state right now * @param timeStampNs time of this event in nanoseconds diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/TypeClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/TypeClassifier.java index b6ceab559301..5f1b37ae06d9 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/TypeClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/TypeClassifier.java @@ -58,4 +58,9 @@ public class TypeClassifier extends FalsingClassifier { return true; } } + + @Override + String getReason() { + return String.format("{vertical=%s, up=%s, right=%s}", isVertical(), isUp(), isRight()); + } } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/ZigZagClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/ZigZagClassifier.java index a0da988b27b5..957ea8d127d2 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/ZigZagClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/ZigZagClassifier.java @@ -29,6 +29,7 @@ import com.android.systemui.util.DeviceConfigProxy; import java.util.ArrayList; import java.util.List; +import java.util.Locale; /** * Penalizes gestures that change direction in either the x or y too much. @@ -49,6 +50,10 @@ class ZigZagClassifier extends FalsingClassifier { private final float mMaxYPrimaryDeviance; private final float mMaxXSecondaryDeviance; private final float mMaxYSecondaryDeviance; + private float mLastDevianceX; + private float mLastDevianceY; + private float mLastMaxXDeviance; + private float mLastMaxYDeviance; ZigZagClassifier(FalsingDataProvider dataProvider, DeviceConfigProxy deviceConfigProxy) { super(dataProvider); @@ -139,11 +144,25 @@ class ZigZagClassifier extends FalsingClassifier { maxYDeviance = mMaxYPrimaryDeviance * totalDistanceIn * getYdpi(); } + // These values are saved for logging reasons. {@see #getReason()} + mLastDevianceX = devianceX; + mLastDevianceY = devianceY; + mLastMaxXDeviance = maxXDeviance; + mLastMaxYDeviance = maxYDeviance; + logDebug("Straightness Deviance: (" + devianceX + "," + devianceY + ") vs " + "(" + maxXDeviance + "," + maxYDeviance + ")"); return devianceX > maxXDeviance || devianceY > maxYDeviance; } + @Override + String getReason() { + return String.format( + (Locale) null, + "{devianceX=%f, maxDevianceX=%s, devianceY=%s, maxDevianceY=%s}", + mLastDevianceX, mLastMaxXDeviance, mLastDevianceY, mLastMaxYDeviance); + } + private float getAtan2LastPoint() { MotionEvent firstEvent = getFirstMotionEvent(); MotionEvent lastEvent = getLastMotionEvent(); diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java index c3672868ba32..bb8c7f1dabf8 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.keyguard.WakefulnessLifecycle; 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 @@ public class DozeFactory { /** 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 @@ public class DozeFactory { 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 026a62528c8d..67eefc5588e3 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.ContentResolver; 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 androidx.annotation.VisibleForTesting; 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 @@ public class DozeSensors { 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 @@ public class DozeSensors { 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 @@ public class DozeSensors { mWakeLock = wakeLock; mProxCallback = proxCallback; mResolver = mContext.getContentResolver(); + mCallback = callback; boolean alwaysOn = mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT); mSensors = new TriggerSensor[] { @@ -146,8 +147,11 @@ public class DozeSensors { 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 class DozeSensors { } 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 @@ public class DozeSensors { /** 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 bab64db4519c..80d4b631314a 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.Context; 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 android.util.Log; 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 @@ public class DozeTriggers implements DozeMachine.Part { */ 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 @@ public class DozeTriggers implements DozeMachine.Part { 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 class DozeTriggers implements DozeMachine.Part { 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 @@ public class DozeTriggers implements DozeMachine.Part { 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 @@ public class DozeTriggers implements DozeMachine.Part { } } - 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 @@ public class DozeTriggers implements DozeMachine.Part { } } else { proximityCheckThenCall((result) -> { - if (result == ProximityCheck.RESULT_NEAR) { + if (result) { // In pocket, drop event. return; } @@ -267,7 +271,7 @@ public class DozeTriggers implements DozeMachine.Part { if (wake) { proximityCheckThenCall((result) -> { - if (result == ProximityCheck.RESULT_NEAR) { + if (result) { // In pocket, drop event. return; } @@ -376,7 +380,7 @@ public class DozeTriggers implements DozeMachine.Part { 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 @@ public class DozeTriggers implements DozeMachine.Part { 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 30be7754cffc..6795bff6409a 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java @@ -18,6 +18,7 @@ package com.android.systemui.pip; 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; @@ -46,9 +47,6 @@ public class PipBoundsHandler { 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; @@ -58,7 +56,7 @@ public class PipBoundsHandler { private final Point mTmpDisplaySize = new Point(); private IPinnedStackController mPinnedStackController; - private int mLastPipToken; + private ComponentName mLastPipComponentName; private float mReentrySnapFraction = INVALID_SNAP_FRACTION; private float mDefaultAspectRatio; @@ -80,8 +78,11 @@ public class PipBoundsHandler { mContext = context; mSnapAlgorithm = new PipSnapAlgorithm(context); mWindowManager = WindowManagerGlobal.getWindowManagerService(); - mAspectRatio = mDefaultAspectRatio; 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; } /** @@ -161,27 +162,27 @@ public class PipBoundsHandler { } /** - * Responds to IPinnedStackListener on saving reentry snap fraction for a given token. - * Token should be generated via {@link System#identityHashCode(Object)} + * Responds to IPinnedStackListener on saving reentry snap fraction + * for a given {@link ComponentName}. */ - public void onSaveReentrySnapFraction(int token, Rect stackBounds) { - mReentrySnapFraction = getSnapFraction(stackBounds); - mLastPipToken = token; + public void onSaveReentrySnapFraction(ComponentName componentName, Rect bounds) { + mReentrySnapFraction = getSnapFraction(bounds); + mLastPipComponentName = componentName; } /** - * Responds to IPinnedStackListener on resetting reentry snap fraction for a given token. - * Token should be generated via {@link System#identityHashCode(Object)} + * Responds to IPinnedStackListener on resetting reentry snap fraction + * for a given {@link ComponentName}. */ - public void onResetReentrySnapFraction(int token) { - if (mLastPipToken == token) { + public void onResetReentrySnapFraction(ComponentName componentName) { + if (componentName.equals(mLastPipComponentName)) { onResetReentrySnapFractionUnchecked(); } } private void onResetReentrySnapFractionUnchecked() { mReentrySnapFraction = INVALID_SNAP_FRACTION; - mLastPipToken = INVALID_SYSTEM_IDENTITY_TOKEN; + mLastPipComponentName = null; } /** @@ -212,24 +213,28 @@ public class PipBoundsHandler { /** * Responds to IPinnedStackListener on preparing the pinned stack animation. */ - public void onPrepareAnimation(Rect sourceRectHint, float aspectRatio, Rect stackBounds) { - final Rect targetStackBounds; - if (stackBounds == null) { - targetStackBounds = getDefaultBounds(mReentrySnapFraction); + public void onPrepareAnimation(Rect sourceRectHint, float aspectRatio, Rect bounds) { + final Rect destinationBounds; + if (bounds == null) { + destinationBounds = getDefaultBounds(mReentrySnapFraction); } else { - targetStackBounds = new Rect(); - targetStackBounds.set(stackBounds); + destinationBounds = new Rect(bounds); } if (isValidPictureInPictureAspectRatio(aspectRatio)) { - transformBoundsToAspectRatio(targetStackBounds, aspectRatio, - true /* useCurrentMinEdgeSize */); + transformBoundsToAspectRatio(destinationBounds, aspectRatio, + false /* useCurrentMinEdgeSize */); } - if (targetStackBounds.equals(stackBounds)) { + if (destinationBounds.equals(bounds)) { return; } mAspectRatio = aspectRatio; onResetReentrySnapFractionUnchecked(); - // TODO: callback Window Manager on starting animation with calculated bounds + try { + mPinnedStackController.startAnimation(destinationBounds, sourceRectHint, + -1 /* animationDuration */); + } catch (RemoteException e) { + Log.e(TAG, "Failed to start PiP animation from SysUI", e); + } } /** @@ -358,6 +363,7 @@ public class PipBoundsHandler { 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 3be3422a36ad..8dfae32a1939 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java @@ -32,14 +32,16 @@ import android.os.Handler; 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; @@ -58,8 +60,12 @@ public class PipManager implements BasePipManager { private IActivityTaskManager mActivityTaskManager; private Handler mHandler = new Handler(); - private final PinnedStackListener mPinnedStackListener = new PinnedStackListener(); + 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 PipBoundsHandler mPipBoundsHandler; private InputConsumerController mInputConsumerController; private PipMenuActivityController mMenuController; private PipMediaController mMediaController; @@ -119,11 +125,11 @@ public class PipManager implements BasePipManager { /** * Handler for messages from the PIP controller. */ - private class PinnedStackListener extends IPinnedStackListener.Stub { - + private class PipManagerPinnedStackListener extends PinnedStackListener { @Override public void onListenerRegistered(IPinnedStackController controller) { mHandler.post(() -> { + mPipBoundsHandler.setPinnedStackController(controller); mTouchHandler.setPinnedStackController(controller); }); } @@ -131,6 +137,7 @@ public class PipManager implements BasePipManager { @Override public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) { mHandler.post(() -> { + mPipBoundsHandler.onImeVisibilityChanged(imeVisible, imeHeight); mTouchHandler.onImeVisibilityChanged(imeVisible, imeHeight); }); } @@ -138,31 +145,66 @@ public class PipManager implements BasePipManager { @Override public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) { mHandler.post(() -> { - mTouchHandler.onShelfVisibilityChanged(shelfVisible, shelfHeight); + mPipBoundsHandler.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 insetBounds, Rect normalBounds, - Rect animatingBounds, boolean fromImeAdjustment, boolean fromShelfAdjustment, - int displayRotation) { + public void onMovementBoundsChanged(Rect animatingBounds, boolean fromImeAdjustment, + boolean fromShelfAdjustment) { mHandler.post(() -> { - mTouchHandler.onMovementBoundsChanged(insetBounds, normalBounds, animatingBounds, - fromImeAdjustment, fromShelfAdjustment, displayRotation); + // 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); }); } @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(() -> { - mMenuController.setAppActions(actions); + mPipBoundsHandler.onPrepareAnimation(sourceRectHint, aspectRatio, bounds); }); } } @@ -184,12 +226,13 @@ public class PipManager implements BasePipManager { } 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); + mMenuController, mInputConsumerController, mPipBoundsHandler); mAppOpsListener = new PipAppOpsListener(context, mActivityManager, mTouchHandler.getMotionHelper()); @@ -252,5 +295,6 @@ public class PipManager implements BasePipManager { 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 30cf412671bc..1f36d97ce308 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -48,6 +48,7 @@ import android.view.accessibility.AccessibilityWindowInfo; 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; @@ -75,6 +76,7 @@ public class PipTouchHandler { 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; @@ -178,7 +180,8 @@ public class PipTouchHandler { public PipTouchHandler(Context context, IActivityManager activityManager, IActivityTaskManager activityTaskManager, PipMenuActivityController menuController, - InputConsumerController inputConsumerController) { + InputConsumerController inputConsumerController, + PipBoundsHandler pipBoundsHandler) { // Initialize the Pip input consumer mContext = context; @@ -211,6 +214,8 @@ public class PipTouchHandler { inputConsumerController.setInputListener(this::handleTouchEvent); inputConsumerController.setRegistrationListener(this::onRegistrationChanged); onRegistrationChanged(inputConsumerController.isRegistered()); + + mPipBoundsHandler = pipBoundsHandler; } public void setTouchEnabled(boolean enabled) { @@ -787,14 +792,8 @@ public class PipTouchHandler { mMovementBounds = isMenuExpanded ? mExpandedMovementBounds : mNormalMovementBounds; - try { - if (mPinnedStackController != null) { - mPinnedStackController.setMinEdgeSize( - isMenuExpanded ? mExpandedShortestEdgeSize : 0); - } - } catch (RemoteException e) { - Log.e(TAG, "Could not set minimized state", e); - } + mPipBoundsHandler.setMinEdgeSize( + isMenuExpanded ? mExpandedShortestEdgeSize : 0); } /** 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 918af4f0cd4c..81d6973efb2a 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java @@ -19,13 +19,10 @@ package com.android.systemui.pip.tv; 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; @@ -46,16 +43,15 @@ import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; import android.util.Pair; -import android.view.IPinnedStackController; -import android.view.IPinnedStackListener; -import android.view.IWindowManager; -import android.view.WindowManagerGlobal; +import android.view.DisplayInfo; 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; @@ -111,9 +107,8 @@ public class PipManager implements BasePipManager { private int mSuspendPipResizingReason; private Context mContext; - private IActivityManager mActivityManager; + private PipBoundsHandler mPipBoundsHandler; private IActivityTaskManager mActivityTaskManager; - private IWindowManager mWindowManager; private MediaSessionManager mMediaSessionManager; private int mState = STATE_NO_PIP; private int mResumeResizePinnedStackRunnableState = STATE_NO_PIP; @@ -135,11 +130,16 @@ public class PipManager implements BasePipManager { 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 PinnedStackListener(); + private final PinnedStackListener mPinnedStackListener = new PipManagerPinnedStackListener(); private final Runnable mResizePinnedStackRunnable = new Runnable() { @Override @@ -181,11 +181,7 @@ public class PipManager implements BasePipManager { /** * Handler for messages from the PIP controller. */ - private class PinnedStackListener extends IPinnedStackListener.Stub { - - @Override - public void onListenerRegistered(IPinnedStackController controller) {} - + private class PipManagerPinnedStackListener extends PinnedStackListener { @Override public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) { if (mState == STATE_PIP) { @@ -205,17 +201,13 @@ public class PipManager implements BasePipManager { } @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) { + public void onMovementBoundsChanged(Rect animatingBounds, boolean fromImeAdjustment, + boolean fromShelfAdjustment) { mHandler.post(() -> { - mDefaultPipBounds.set(normalBounds); + // Populate the inset / normal bounds and DisplayInfo from mPipBoundsHandler first. + mPipBoundsHandler.onMovementBoundsChanged(mTmpInsetBounds, mTmpNormalBounds, + animatingBounds, mTmpDisplayInfo); + mDefaultPipBounds.set(animatingBounds); }); } @@ -241,10 +233,8 @@ public class PipManager implements BasePipManager { } mInitialized = true; mContext = context; - - mActivityManager = ActivityManager.getService(); + mPipBoundsHandler = new PipBoundsHandler(context); mActivityTaskManager = ActivityTaskManager.getService(); - mWindowManager = WindowManagerGlobal.getWindowManagerService(); ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_MEDIA_RESOURCE_GRANTED); @@ -291,7 +281,7 @@ public class PipManager implements BasePipManager { (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE); try { - mWindowManager.registerPinnedStackListener(DEFAULT_DISPLAY, mPinnedStackListener); + WindowManagerWrapper.getInstance().addPinnedStackListener(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 61d7498ced94..1e763cf79240 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 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D 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 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D 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 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D 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 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D newTiles.put(tileSpec, tile); } else { tile.destroy(); + Log.d(TAG, "Destroying not available tile: " + tileSpec); } } } catch (Throwable t) { @@ -274,7 +276,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D 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/MediaTransferManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/MediaTransferManager.java index 752b6a80f93d..926ae71194d9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/MediaTransferManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/MediaTransferManager.java @@ -18,12 +18,19 @@ package com.android.systemui.statusbar; import android.content.Context; import android.content.Intent; +import android.content.res.ColorStateList; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.RippleDrawable; import android.service.notification.StatusBarNotification; import android.util.FeatureFlagUtils; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import com.android.internal.R; import com.android.settingslib.media.MediaOutputSliceConstants; import com.android.systemui.Dependency; import com.android.systemui.plugins.ActivityStarter; @@ -51,7 +58,8 @@ public class MediaTransferManager { } ViewParent parent = view.getParent(); - StatusBarNotification statusBarNotification = getNotificationForParent(parent); + StatusBarNotification statusBarNotification = + getRowForParent(parent).getStatusBarNotification(); final Intent intent = new Intent() .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT) .putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME, @@ -60,16 +68,6 @@ public class MediaTransferManager { Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); return true; } - - private StatusBarNotification getNotificationForParent(ViewParent parent) { - while (parent != null) { - if (parent instanceof ExpandableNotificationRow) { - return ((ExpandableNotificationRow) parent).getStatusBarNotification(); - } - parent = parent.getParent(); - } - return null; - } }; public MediaTransferManager(Context context) { @@ -77,6 +75,16 @@ public class MediaTransferManager { mActivityStarter = Dependency.get(ActivityStarter.class); } + private ExpandableNotificationRow getRowForParent(ViewParent parent) { + while (parent != null) { + if (parent instanceof ExpandableNotificationRow) { + return ((ExpandableNotificationRow) parent); + } + parent = parent.getParent(); + } + return null; + } + /** * apply the action button for MediaTransfer * @@ -95,5 +103,23 @@ public class MediaTransferManager { view.setVisibility(View.VISIBLE); view.setOnClickListener(mOnClickHandler); + + ExpandableNotificationRow enr = getRowForParent(view.getParent()); + int color = enr.getNotificationHeader().getOriginalIconColor(); + ColorStateList tintList = ColorStateList.valueOf(color); + + // Update the outline color + LinearLayout viewLayout = (LinearLayout) view; + RippleDrawable bkgDrawable = (RippleDrawable) viewLayout.getBackground(); + GradientDrawable rect = (GradientDrawable) bkgDrawable.getDrawable(0); + rect.setStroke(2, color); + + // Update the image color + ImageView image = view.findViewById(R.id.media_seamless_image); + image.setImageTintList(tintList); + + // Update the text color + TextView text = view.findViewById(R.id.media_seamless_text); + text.setTextColor(tintList); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt index 276afa7a3a94..a70dc7c0ec5d 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.Gefingerpoken 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 @@ constructor( 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 @@ constructor( 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 4422a81874c6..8b9268e1888a 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.ObjectAnimator; 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 @@ public class StatusBarStateControllerImpl implements SysuiStatusBarStateControll // 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/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java index 91d47077fc31..13c6f2730d08 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java @@ -34,7 +34,6 @@ import android.view.View; import com.android.internal.policy.ScreenDecorationsUtils; import com.android.systemui.Interpolators; -import com.android.systemui.shared.system.SurfaceControlCompat; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment; @@ -145,6 +144,7 @@ public class ActivityLaunchAnimator { @Override public void onAnimationStart(RemoteAnimationTarget[] remoteAnimationTargets, + RemoteAnimationTarget[] remoteAnimationWallpaperTargets, IRemoteAnimationFinishedCallback iRemoteAnimationFinishedCallback) throws RemoteException { mSourceNotification.post(() -> { 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 a33d23c0b5d5..c3211e307845 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 @@ -141,6 +141,12 @@ public final class NotificationEntry { 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. */ @@ -205,6 +211,7 @@ public final class NotificationEntry { + " doesn't match existing key " + key); } mRanking = ranking; + isVisuallyInterruptive = ranking.visuallyInterruptive(); } public NotificationChannel getChannel() { @@ -244,6 +251,7 @@ public final class NotificationEntry { return mRanking.canBubble(); } + public @NonNull List<Notification.Action> getSmartActions() { return mRanking.getSmartActions(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java index 48a82957bf1e..a612a1721c41 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java @@ -264,7 +264,7 @@ public class NotificationContentInflater { mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, packageContext); result = inflateSmartReplyViews(result, reInflateFlags, mRow.getEntry(), - mRow.getContext(), mRow.getHeadsUpManager(), + mRow.getContext(), packageContext, mRow.getHeadsUpManager(), mRow.getExistingSmartRepliesAndActions()); apply( inflateSynchronously, @@ -311,20 +311,21 @@ public class NotificationContentInflater { private static InflationProgress inflateSmartReplyViews(InflationProgress result, @InflationFlag int reInflateFlags, NotificationEntry entry, Context context, - HeadsUpManager headsUpManager, SmartRepliesAndActions previousSmartRepliesAndActions) { + Context packageContext, HeadsUpManager headsUpManager, + SmartRepliesAndActions previousSmartRepliesAndActions) { SmartReplyConstants smartReplyConstants = Dependency.get(SmartReplyConstants.class); SmartReplyController smartReplyController = Dependency.get(SmartReplyController.class); if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0 && result.newExpandedView != null) { result.expandedInflatedSmartReplies = InflatedSmartReplies.inflate( - context, entry, smartReplyConstants, smartReplyController, - headsUpManager, previousSmartRepliesAndActions); + context, packageContext, entry, smartReplyConstants, + smartReplyController, headsUpManager, previousSmartRepliesAndActions); } if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0 && result.newHeadsUpView != null) { result.headsUpInflatedSmartReplies = InflatedSmartReplies.inflate( - context, entry, smartReplyConstants, smartReplyController, - headsUpManager, previousSmartRepliesAndActions); + context, packageContext, entry, smartReplyConstants, + smartReplyController, headsUpManager, previousSmartRepliesAndActions); } return result; } @@ -817,7 +818,7 @@ public class NotificationContentInflater { recoveredBuilder, mIsLowPriority, mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, packageContext); return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mRow.getEntry(), - mRow.getContext(), mRow.getHeadsUpManager(), + mRow.getContext(), packageContext, mRow.getHeadsUpManager(), mRow.getExistingSmartRepliesAndActions()); } catch (Exception e) { mError = e; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index cc2078bd158b..f30a8b12ab39 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -1139,19 +1139,21 @@ public class NotificationContentView extends FrameLayout { } private void applyMediaTransfer(final NotificationEntry entry) { - View bigContentView = mExpandedChild; - if (bigContentView == null || !entry.isMediaNotification()) { + if (!entry.isMediaNotification()) { return; } - View mediaActionContainer = bigContentView.findViewById( - com.android.internal.R.id.media_actions); - if (!(mediaActionContainer instanceof LinearLayout)) { - return; + View bigContentView = mExpandedChild; + if (bigContentView != null && (bigContentView instanceof ViewGroup)) { + mMediaTransferManager.applyMediaTransferView((ViewGroup) bigContentView, + entry); } - mMediaTransferManager.applyMediaTransferView((ViewGroup) mediaActionContainer, - entry); + View smallContentView = mContractedChild; + if (smallContentView != null && (smallContentView instanceof ViewGroup)) { + mMediaTransferManager.applyMediaTransferView((ViewGroup) smallContentView, + entry); + } } private void applyRemoteInputAndSmartReply(final NotificationEntry entry) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index 41c6a7ba7848..2d012c93f42b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -448,9 +448,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback { } else if (!unlockingAllowed) { return bypass ? MODE_SHOW_BOUNCER : MODE_NONE; } else if (mDozeScrimController.isPulsing()) { - // Let's not wake-up to lock screen when not bypassing, otherwise the notification - // would move as the user tried to tap it. - return bypass ? MODE_WAKE_AND_UNLOCK_PULSING : MODE_NONE; + return bypass ? MODE_WAKE_AND_UNLOCK_PULSING : MODE_ONLY_WAKE; } else { if (bypass) { // Wake-up fading out nicely 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 575b5597b657..4cd3ad27ab34 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java @@ -18,7 +18,6 @@ package com.android.systemui.statusbar.phone; 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; @@ -36,8 +35,6 @@ import android.util.Log; 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; @@ -57,6 +54,7 @@ import com.android.systemui.R; 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; @@ -72,35 +70,13 @@ public class EdgeBackGestureHandler implements DisplayListener { private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt( "gestures.back_timeout", 250); - private final IPinnedStackListener.Stub mImeChangedListener = new IPinnedStackListener.Stub() { - @Override - public void onListenerRegistered(IPinnedStackController controller) { - } - + private final PinnedStackListener mImeChangedListener = new PinnedStackListener() { @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 f7bb97b38dd5..00b764bfbe9b 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.keyguard.KeyguardUpdateMonitorCallback 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/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java index ba3406999388..1a3560ece1d7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java @@ -537,11 +537,7 @@ public class NotificationIconAreaController implements DarkReceiver, if (dozeParameters.shouldControlScreenOff()) { mAodIcons.setTranslationY(-mAodIconAppearTranslation); mAodIcons.setAlpha(0); - mAodIcons.animate() - .setInterpolator(Interpolators.DECELERATE_QUINT) - .translationY(0) - .setDuration(AOD_ICONS_APPEAR_DURATION) - .start(); + animateInAodIconTranslation(); mAodIcons.animate() .alpha(1) .setInterpolator(Interpolators.LINEAR) @@ -550,6 +546,14 @@ public class NotificationIconAreaController implements DarkReceiver, } } + private void animateInAodIconTranslation() { + mAodIcons.animate() + .setInterpolator(Interpolators.DECELERATE_QUINT) + .translationY(0) + .setDuration(AOD_ICONS_APPEAR_DURATION) + .start(); + } + private void reloadAodColor() { mAodIconTint = Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor); @@ -606,14 +610,19 @@ public class NotificationIconAreaController implements DarkReceiver, mAodIcons.setAlpha(1.0f); appearAodIcons(); } else { + // Let's make sure the icon are translated to 0, since we cancelled it above + animateInAodIconTranslation(); // We were fading out, let's fade in instead CrossFadeHelper.fadeIn(mAodIcons); } } else { + // Let's make sure the icon are translated to 0, since we cancelled it above + animateInAodIconTranslation(); CrossFadeHelper.fadeOut(mAodIcons); } } else { mAodIcons.setAlpha(1.0f); + mAodIcons.setTranslationY(0); mAodIcons.setVisibility(visible ? View.VISIBLE : View.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 f853b638db46..8c95b844ae2f 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 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo * 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/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java index 40c3d9d19c16..0a2fb2e783a9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.policy; import static com.android.systemui.Dependency.BG_LOOPER_NAME; +import static com.android.systemui.Dependency.MAIN_LOOPER_NAME; import android.annotation.Nullable; import android.app.ActivityManager; @@ -67,16 +68,18 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa private boolean mEnabled; private int mConnectionState = BluetoothAdapter.STATE_DISCONNECTED; - private final H mHandler = new H(Looper.getMainLooper()); + private final H mHandler; private int mState; /** */ @Inject public BluetoothControllerImpl(Context context, @Named(BG_LOOPER_NAME) Looper bgLooper, + @Named(MAIN_LOOPER_NAME) Looper mainLooper, @Nullable LocalBluetoothManager localBluetoothManager) { mLocalBluetoothManager = localBluetoothManager; mBgHandler = new Handler(bgLooper); + mHandler = new H(mainLooper); if (mLocalBluetoothManager != null) { mLocalBluetoothManager.getEventManager().registerCallback(this); mLocalBluetoothManager.getProfileManager().addServiceListener(this); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java index 25a32b330f7d..be27741b4dc8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java @@ -80,6 +80,7 @@ public class InflatedSmartReplies { */ public static InflatedSmartReplies inflate( Context context, + Context packageContext, NotificationEntry entry, SmartReplyConstants smartReplyConstants, SmartReplyController smartReplyController, @@ -108,9 +109,9 @@ public class InflatedSmartReplies { } if (newSmartRepliesAndActions.smartActions != null) { suggestionButtons.addAll( - smartReplyView.inflateSmartActions(newSmartRepliesAndActions.smartActions, - smartReplyController, entry, headsUpManager, - delayOnClickListener)); + smartReplyView.inflateSmartActions(packageContext, + newSmartRepliesAndActions.smartActions, smartReplyController, entry, + headsUpManager, delayOnClickListener)); } return new InflatedSmartReplies(smartReplyView, suggestionButtons, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java index dbfb09f7fc41..5da726749580 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java @@ -294,7 +294,8 @@ public class MobileSignalController extends SignalController< } boolean dataDisabled = mCurrentState.userSetup && (mCurrentState.iconGroup == TelephonyIcons.DATA_DISABLED - || mCurrentState.iconGroup == TelephonyIcons.NOT_DEFAULT_DATA); + || (mCurrentState.iconGroup == TelephonyIcons.NOT_DEFAULT_DATA + && mCurrentState.defaultDataOff)); boolean noInternet = mCurrentState.inetCondition == 0; boolean cutOut = dataDisabled || noInternet; return SignalDrawable.getState(level, getNumLevels(), cutOut); @@ -320,7 +321,7 @@ public class MobileSignalController extends SignalController< dataContentDescription = mContext.getString(R.string.data_connection_no_internet); } final boolean dataDisabled = (mCurrentState.iconGroup == TelephonyIcons.DATA_DISABLED - || mCurrentState.iconGroup == TelephonyIcons.NOT_DEFAULT_DATA) + || (mCurrentState.iconGroup == TelephonyIcons.NOT_DEFAULT_DATA)) && mCurrentState.userSetup; // Show icon in QS when we are connected or data is disabled. @@ -484,6 +485,7 @@ public class MobileSignalController extends SignalController< Log.d(mTag, "updateTelephonySignalStrength: hasService=" + Utils.isInService(mServiceState) + " ss=" + mSignalStrength); } + checkDefaultData(); mCurrentState.connected = Utils.isInService(mServiceState) && mSignalStrength != null; if (mCurrentState.connected) { @@ -541,6 +543,23 @@ public class MobileSignalController extends SignalController< notifyListenersIfNecessary(); } + /** + * If we are controlling the NOT_DEFAULT_DATA icon, check the status of the other one + */ + private void checkDefaultData() { + if (mCurrentState.iconGroup != TelephonyIcons.NOT_DEFAULT_DATA) { + mCurrentState.defaultDataOff = false; + return; + } + + mCurrentState.defaultDataOff = mNetworkController.isDataControllerDisabled(); + } + + void onMobileDataChanged() { + checkDefaultData(); + notifyListenersIfNecessary(); + } + private MobileIconGroup getNr5GIconGroup() { if (mServiceState == null) return null; @@ -617,7 +636,7 @@ public class MobileSignalController extends SignalController< return candidateIconGroup; } - private boolean isDataDisabled() { + boolean isDataDisabled() { return !mPhone.isDataCapable(); } @@ -750,6 +769,7 @@ public class MobileSignalController extends SignalController< boolean isDefault; boolean userSetup; boolean roaming; + boolean defaultDataOff; // Tracks the on/off state of the defaultDataSubscription @Override public void copyFrom(State s) { @@ -765,6 +785,7 @@ public class MobileSignalController extends SignalController< carrierNetworkChangeMode = state.carrierNetworkChangeMode; userSetup = state.userSetup; roaming = state.roaming; + defaultDataOff = state.defaultDataOff; } @Override @@ -781,7 +802,8 @@ public class MobileSignalController extends SignalController< builder.append("airplaneMode=").append(airplaneMode).append(','); builder.append("carrierNetworkChangeMode=").append(carrierNetworkChangeMode) .append(','); - builder.append("userSetup=").append(userSetup); + builder.append("userSetup=").append(userSetup).append(','); + builder.append("defaultDataOff=").append(defaultDataOff); } @Override @@ -796,7 +818,8 @@ public class MobileSignalController extends SignalController< && ((MobileState) o).carrierNetworkChangeMode == carrierNetworkChangeMode && ((MobileState) o).userSetup == userSetup && ((MobileState) o).isDefault == isDefault - && ((MobileState) o).roaming == roaming; + && ((MobileState) o).roaming == roaming + && ((MobileState) o).defaultDataOff == defaultDataOff; } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index e1b3816cf759..7b5d48947498 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -220,6 +220,7 @@ public class NetworkControllerImpl extends BroadcastReceiver @Override public void onMobileDataEnabled(boolean enabled) { mCallbackHandler.setMobileDataEnabled(enabled); + notifyControllersMobileDataChanged(); } }); mWifiSignalController = new WifiSignalController(mContext, mHasMobileDataFeature, @@ -386,6 +387,22 @@ public class NetworkControllerImpl extends BroadcastReceiver return mMobileSignalControllers.size(); } + boolean isDataControllerDisabled() { + MobileSignalController dataController = getDataController(); + if (dataController == null) { + return false; + } + + return dataController.isDataDisabled(); + } + + private void notifyControllersMobileDataChanged() { + for (int i = 0; i < mMobileSignalControllers.size(); i++) { + MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i); + mobileSignalController.onMobileDataChanged(); + } + } + public boolean isEmergencyOnly() { if (mMobileSignalControllers.size() == 0) { // When there are no active subscriptions, determine emengency state from last diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java index 4fdaa6300065..b5f660a84043 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java @@ -235,17 +235,17 @@ public class SmartReplyView extends ViewGroup { * Add smart actions to be shown next to smart replies. Only the actions that fit into the * notification are shown. */ - public List<Button> inflateSmartActions(@NonNull SmartActions smartActions, - SmartReplyController smartReplyController, NotificationEntry entry, - HeadsUpManager headsUpManager, boolean delayOnClickListener) { + public List<Button> inflateSmartActions(Context packageContext, + @NonNull SmartActions smartActions, SmartReplyController smartReplyController, + NotificationEntry entry, HeadsUpManager headsUpManager, boolean delayOnClickListener) { List<Button> buttons = new ArrayList<>(); int numSmartActions = smartActions.actions.size(); for (int n = 0; n < numSmartActions; n++) { Notification.Action action = smartActions.actions.get(n); if (action.actionIntent != null) { buttons.add(inflateActionButton( - this, getContext(), n, smartActions, smartReplyController, entry, - headsUpManager, delayOnClickListener)); + this, getContext(), packageContext, n, smartActions, smartReplyController, + entry, headsUpManager, delayOnClickListener)); } } return buttons; @@ -327,7 +327,7 @@ public class SmartReplyView extends ViewGroup { @VisibleForTesting static Button inflateActionButton(SmartReplyView smartReplyView, Context context, - int actionIndex, SmartActions smartActions, + Context packageContext, int actionIndex, SmartActions smartActions, SmartReplyController smartReplyController, NotificationEntry entry, HeadsUpManager headsUpManager, boolean useDelayedOnClickListener) { Notification.Action action = smartActions.actions.get(actionIndex); @@ -335,7 +335,9 @@ public class SmartReplyView extends ViewGroup { R.layout.smart_action_button, smartReplyView, false); button.setText(action.title); - Drawable iconDrawable = action.getIcon().loadDrawable(context); + // We received the Icon from the application - so use the Context of the application to + // reference icon resources. + Drawable iconDrawable = action.getIcon().loadDrawable(packageContext); // Add the action icon to the Smart Action button. int newIconSize = context.getResources().getDimensionPixelSize( R.dimen.smart_action_button_icon_size); diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java index 97507900e269..453c2f7da71f 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java @@ -31,15 +31,21 @@ import androidx.preference.PreferenceScreen; import com.android.systemui.Dependency; import com.android.systemui.R; -import com.android.systemui.SystemUIFactory; import com.android.systemui.fragments.FragmentService; +import javax.inject.Inject; + public class TunerActivity extends Activity implements PreferenceFragment.OnPreferenceStartFragmentCallback, PreferenceFragment.OnPreferenceStartScreenCallback { private static final String TAG_TUNER = "tuner"; + @Inject + TunerActivity() { + super(); + } + protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -51,8 +57,6 @@ public class TunerActivity extends Activity implements setActionBar(toolbar); } - Dependency.initDependencies(SystemUIFactory.getInstance().getRootComponent()); - if (getFragmentManager().findFragmentByTag(TAG_TUNER) == null) { final String action = getIntent().getAction(); boolean showDemoMode = action != null && action.equals( @@ -68,7 +72,6 @@ public class TunerActivity extends Activity implements protected void onDestroy() { super.onDestroy(); Dependency.destroy(FragmentService.class, s -> s.destroyAll()); - Dependency.clearDependencies(); } @Override 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 a905eba1f0ed..000000000000 --- 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 index b9c5ee5a7a7e..dcd0c58a5310 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 @@ public class AsyncSensorManager extends SensorManager 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 000000000000..c48bdde6adef --- /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/DependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java index b9d09ce91c1a..939df10724ca 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java @@ -74,6 +74,9 @@ public class DependencyTest extends SysuiTestCase { @Test public void testInitDependency() { Dependency.clearDependencies(); - Dependency.initDependencies(SystemUIFactory.getInstance().getRootComponent()); + Dependency dependency = new Dependency(); + SystemUIFactory + .getInstance().getRootComponent().createDependency().createSystemUI(dependency); + dependency.start(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleLikeHomeBehaviorTest.java b/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleLikeHomeBehaviorTest.java index c53289c62d94..fe131275afd8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleLikeHomeBehaviorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleLikeHomeBehaviorTest.java @@ -31,6 +31,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.model.SysUiState; +import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shared.system.QuickStepContract; import org.junit.Before; @@ -47,6 +48,7 @@ public class AssistHandleLikeHomeBehaviorTest extends SysuiTestCase { private AssistHandleLikeHomeBehavior mAssistHandleLikeHomeBehavior; + @Mock private StatusBarStateController mMockStatusBarStateController; @Mock private WakefulnessLifecycle mMockWakefulnessLifecycle; @Mock private SysUiState mMockSysUiState; @Mock private AssistHandleCallbacks mMockAssistHandleCallbacks; @@ -55,7 +57,9 @@ public class AssistHandleLikeHomeBehaviorTest extends SysuiTestCase { public void setup() { MockitoAnnotations.initMocks(this); mAssistHandleLikeHomeBehavior = new AssistHandleLikeHomeBehavior( - () -> mMockWakefulnessLifecycle, () -> mMockSysUiState); + () -> mMockStatusBarStateController, + () -> mMockWakefulnessLifecycle, + () -> mMockSysUiState); } @Test @@ -66,6 +70,9 @@ public class AssistHandleLikeHomeBehaviorTest extends SysuiTestCase { mAssistHandleLikeHomeBehavior.onModeActivated(mContext, mMockAssistHandleCallbacks); // Assert + verify(mMockStatusBarStateController).isDozing(); + verify(mMockStatusBarStateController).addCallback( + any(StatusBarStateController.StateListener.class)); verify(mMockWakefulnessLifecycle).getWakefulness(); verify(mMockWakefulnessLifecycle).addObserver(any(WakefulnessLifecycle.Observer.class)); verify(mMockSysUiState).addCallback(any(SysUiState.SysUiStateCallback.class)); @@ -73,8 +80,9 @@ public class AssistHandleLikeHomeBehaviorTest extends SysuiTestCase { } @Test - public void onModeActivated_showsHandlesWhenAwake() { + public void onModeActivated_showsHandlesWhenFullyAwake() { // Arrange + when(mMockStatusBarStateController.isDozing()).thenReturn(false); when(mMockWakefulnessLifecycle.getWakefulness()) .thenReturn(WakefulnessLifecycle.WAKEFULNESS_AWAKE); @@ -89,6 +97,7 @@ public class AssistHandleLikeHomeBehaviorTest extends SysuiTestCase { @Test public void onModeActivated_hidesHandlesWhenNotAwake() { // Arrange + when(mMockStatusBarStateController.isDozing()).thenReturn(true); when(mMockWakefulnessLifecycle.getWakefulness()) .thenReturn(WakefulnessLifecycle.WAKEFULNESS_ASLEEP); @@ -101,72 +110,139 @@ public class AssistHandleLikeHomeBehaviorTest extends SysuiTestCase { } @Test + public void onModeActivated_hidesHandlesWhenDozing() { + // Arrange + when(mMockStatusBarStateController.isDozing()).thenReturn(true); + when(mMockWakefulnessLifecycle.getWakefulness()) + .thenReturn(WakefulnessLifecycle.WAKEFULNESS_AWAKE); + + // Act + mAssistHandleLikeHomeBehavior.onModeActivated(mContext, mMockAssistHandleCallbacks); + + // Assert + verify(mMockAssistHandleCallbacks).hide(); + verifyNoMoreInteractions(mMockAssistHandleCallbacks); + } + + @Test public void onModeDeactivated_stopsObserving() { // Arrange mAssistHandleLikeHomeBehavior.onModeActivated(mContext, mMockAssistHandleCallbacks); + ArgumentCaptor<StatusBarStateController.StateListener> stateListener = + ArgumentCaptor.forClass(StatusBarStateController.StateListener.class); ArgumentCaptor<WakefulnessLifecycle.Observer> observer = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer.class); ArgumentCaptor<SysUiState.SysUiStateCallback> sysUiStateCallback = ArgumentCaptor.forClass(SysUiState.SysUiStateCallback.class); + verify(mMockStatusBarStateController).addCallback(stateListener.capture()); verify(mMockWakefulnessLifecycle).addObserver(observer.capture()); verify(mMockSysUiState).addCallback(sysUiStateCallback.capture()); - reset(mMockWakefulnessLifecycle, mMockSysUiState, mMockAssistHandleCallbacks); + reset( + mMockStatusBarStateController, + mMockWakefulnessLifecycle, + mMockSysUiState, + mMockAssistHandleCallbacks); // Act mAssistHandleLikeHomeBehavior.onModeDeactivated(); // Assert + verify(mMockStatusBarStateController).removeCallback(eq(stateListener.getValue())); verify(mMockWakefulnessLifecycle).removeObserver(eq(observer.getValue())); verify(mMockSysUiState).removeCallback(eq(sysUiStateCallback.getValue())); verifyNoMoreInteractions( - mMockWakefulnessLifecycle, mMockSysUiState, mMockAssistHandleCallbacks); + mMockStatusBarStateController, + mMockWakefulnessLifecycle, + mMockSysUiState, + mMockAssistHandleCallbacks); } @Test public void onAssistantGesturePerformed_doesNothing() { // Arrange mAssistHandleLikeHomeBehavior.onModeActivated(mContext, mMockAssistHandleCallbacks); - reset(mMockWakefulnessLifecycle, mMockSysUiState, mMockAssistHandleCallbacks); + reset( + mMockStatusBarStateController, + mMockWakefulnessLifecycle, + mMockSysUiState, + mMockAssistHandleCallbacks); // Act mAssistHandleLikeHomeBehavior.onAssistantGesturePerformed(); // Assert verifyNoMoreInteractions( - mMockWakefulnessLifecycle, mMockSysUiState, mMockAssistHandleCallbacks); + mMockStatusBarStateController, + mMockWakefulnessLifecycle, + mMockSysUiState, + mMockAssistHandleCallbacks); } @Test public void onAssistHandlesRequested_doesNothing() { // Arrange mAssistHandleLikeHomeBehavior.onModeActivated(mContext, mMockAssistHandleCallbacks); - reset(mMockWakefulnessLifecycle, mMockSysUiState, mMockAssistHandleCallbacks); + reset( + mMockStatusBarStateController, + mMockWakefulnessLifecycle, + mMockSysUiState, + mMockAssistHandleCallbacks); // Act mAssistHandleLikeHomeBehavior.onAssistHandlesRequested(); // Assert verifyNoMoreInteractions( - mMockWakefulnessLifecycle, mMockSysUiState, mMockAssistHandleCallbacks); + mMockStatusBarStateController, + mMockWakefulnessLifecycle, + mMockSysUiState, + mMockAssistHandleCallbacks); } @Test - public void onWake_handlesShow() { + public void onBothAwakeAndUnDoze_handlesShow() { // Arrange + when(mMockStatusBarStateController.isDozing()).thenReturn(true); when(mMockWakefulnessLifecycle.getWakefulness()) .thenReturn(WakefulnessLifecycle.WAKEFULNESS_ASLEEP); + ArgumentCaptor<StatusBarStateController.StateListener> stateListener = + ArgumentCaptor.forClass(StatusBarStateController.StateListener.class); ArgumentCaptor<WakefulnessLifecycle.Observer> observer = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer.class); mAssistHandleLikeHomeBehavior.onModeActivated(mContext, mMockAssistHandleCallbacks); + verify(mMockStatusBarStateController).addCallback(stateListener.capture()); verify(mMockWakefulnessLifecycle).addObserver(observer.capture()); - reset(mMockWakefulnessLifecycle, mMockSysUiState, mMockAssistHandleCallbacks); + reset( + mMockStatusBarStateController, + mMockWakefulnessLifecycle, + mMockSysUiState, + mMockAssistHandleCallbacks); // Act - observer.getValue().onStartedWakingUp(); + observer.getValue().onFinishedWakingUp(); // Assert + verify(mMockAssistHandleCallbacks).hide(); verifyNoMoreInteractions( - mMockWakefulnessLifecycle, mMockSysUiState, mMockAssistHandleCallbacks); + mMockStatusBarStateController, + mMockWakefulnessLifecycle, + mMockSysUiState, + mMockAssistHandleCallbacks); + + // Arrange + observer.getValue().onFinishedGoingToSleep(); + reset(mMockAssistHandleCallbacks); + + // Act + stateListener.getValue().onDozingChanged(false); + + // Assert + verify(mMockAssistHandleCallbacks).hide(); + verifyNoMoreInteractions( + mMockStatusBarStateController, + mMockWakefulnessLifecycle, + mMockSysUiState, + mMockAssistHandleCallbacks); // Act observer.getValue().onFinishedWakingUp(); @@ -174,19 +250,30 @@ public class AssistHandleLikeHomeBehaviorTest extends SysuiTestCase { // Assert verify(mMockAssistHandleCallbacks).showAndStay(); verifyNoMoreInteractions( - mMockWakefulnessLifecycle, mMockSysUiState, mMockAssistHandleCallbacks); + mMockStatusBarStateController, + mMockWakefulnessLifecycle, + mMockSysUiState, + mMockAssistHandleCallbacks); } @Test - public void onSleep_handlesHide() { + public void onSleepOrDoze_handlesHide() { // Arrange + when(mMockStatusBarStateController.isDozing()).thenReturn(false); when(mMockWakefulnessLifecycle.getWakefulness()) .thenReturn(WakefulnessLifecycle.WAKEFULNESS_AWAKE); + ArgumentCaptor<StatusBarStateController.StateListener> stateListener = + ArgumentCaptor.forClass(StatusBarStateController.StateListener.class); ArgumentCaptor<WakefulnessLifecycle.Observer> observer = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer.class); mAssistHandleLikeHomeBehavior.onModeActivated(mContext, mMockAssistHandleCallbacks); + verify(mMockStatusBarStateController).addCallback(stateListener.capture()); verify(mMockWakefulnessLifecycle).addObserver(observer.capture()); - reset(mMockWakefulnessLifecycle, mMockSysUiState, mMockAssistHandleCallbacks); + reset( + mMockStatusBarStateController, + mMockWakefulnessLifecycle, + mMockSysUiState, + mMockAssistHandleCallbacks); // Act observer.getValue().onStartedGoingToSleep(); @@ -194,26 +281,42 @@ public class AssistHandleLikeHomeBehaviorTest extends SysuiTestCase { // Assert verify(mMockAssistHandleCallbacks).hide(); verifyNoMoreInteractions( - mMockWakefulnessLifecycle, mMockSysUiState, mMockAssistHandleCallbacks); + mMockStatusBarStateController, + mMockWakefulnessLifecycle, + mMockSysUiState, + mMockAssistHandleCallbacks); + + // Arrange + observer.getValue().onFinishedWakingUp(); + reset(mMockAssistHandleCallbacks); // Act - observer.getValue().onFinishedGoingToSleep(); + stateListener.getValue().onDozingChanged(true); // Assert + verify(mMockAssistHandleCallbacks).hide(); verifyNoMoreInteractions( - mMockWakefulnessLifecycle, mMockSysUiState, mMockAssistHandleCallbacks); + mMockStatusBarStateController, + mMockWakefulnessLifecycle, + mMockSysUiState, + mMockAssistHandleCallbacks); } @Test public void onHomeHandleHide_handlesHide() { // Arrange + when(mMockStatusBarStateController.isDozing()).thenReturn(false); when(mMockWakefulnessLifecycle.getWakefulness()) .thenReturn(WakefulnessLifecycle.WAKEFULNESS_AWAKE); ArgumentCaptor<SysUiState.SysUiStateCallback> sysUiStateCallback = ArgumentCaptor.forClass(SysUiState.SysUiStateCallback.class); mAssistHandleLikeHomeBehavior.onModeActivated(mContext, mMockAssistHandleCallbacks); verify(mMockSysUiState).addCallback(sysUiStateCallback.capture()); - reset(mMockWakefulnessLifecycle, mMockSysUiState, mMockAssistHandleCallbacks); + reset( + mMockStatusBarStateController, + mMockWakefulnessLifecycle, + mMockSysUiState, + mMockAssistHandleCallbacks); // Act sysUiStateCallback.getValue().onSystemUiStateChanged( @@ -222,12 +325,16 @@ public class AssistHandleLikeHomeBehaviorTest extends SysuiTestCase { // Assert verify(mMockAssistHandleCallbacks).hide(); verifyNoMoreInteractions( - mMockWakefulnessLifecycle, mMockSysUiState, mMockAssistHandleCallbacks); + mMockStatusBarStateController, + mMockWakefulnessLifecycle, + mMockSysUiState, + mMockAssistHandleCallbacks); } @Test public void onHomeHandleUnhide_handlesShow() { // Arrange + when(mMockStatusBarStateController.isDozing()).thenReturn(false); when(mMockWakefulnessLifecycle.getWakefulness()) .thenReturn(WakefulnessLifecycle.WAKEFULNESS_AWAKE); ArgumentCaptor<SysUiState.SysUiStateCallback> sysUiStateCallback = @@ -236,7 +343,11 @@ public class AssistHandleLikeHomeBehaviorTest extends SysuiTestCase { verify(mMockSysUiState).addCallback(sysUiStateCallback.capture()); sysUiStateCallback.getValue().onSystemUiStateChanged( QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN); - reset(mMockWakefulnessLifecycle, mMockSysUiState, mMockAssistHandleCallbacks); + reset( + mMockStatusBarStateController, + mMockWakefulnessLifecycle, + mMockSysUiState, + mMockAssistHandleCallbacks); // Act sysUiStateCallback.getValue().onSystemUiStateChanged( @@ -245,6 +356,9 @@ public class AssistHandleLikeHomeBehaviorTest extends SysuiTestCase { // Assert verify(mMockAssistHandleCallbacks).showAndStay(); verifyNoMoreInteractions( - mMockWakefulnessLifecycle, mMockSysUiState, mMockAssistHandleCallbacks); + mMockStatusBarStateController, + mMockWakefulnessLifecycle, + mMockSysUiState, + mMockAssistHandleCallbacks); } } 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 b9793562d8d8..3561e3465898 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.classifier.brightline.BrightLineFalsingManager; 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 3fc5d7202d0a..35d59c1c9726 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 android.view.MotionEvent; 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 0c124fff53a3..752e145d79bd 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.when; 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 392c677b9827..aa62e9aca4fe 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.filters.SmallTest; 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 cd6d1e069566..ddd1685bf8bc 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.SysuiTestCase; 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 e190f9923da8..f7cd69643fc6 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.hardware.display.AmbientDisplayConfiguration; 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 androidx.test.filters.SmallTest; 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 @@ public class DozeTriggersTest extends SysuiTestCase { 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 @@ public class DozeTriggersTest extends SysuiTestCase { @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 @@ public class DozeTriggersTest extends SysuiTestCase { 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 @@ public class DozeTriggersTest extends SysuiTestCase { 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 @@ public class DozeTriggersTest extends SysuiTestCase { 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 @@ public class DozeTriggersTest extends SysuiTestCase { 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/RankingBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java index 05f179ed4620..820f4652e685 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 @@ public class RankingBuilder { 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 @@ public class RankingBuilder { 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 53d6bffdc896..30e02e6b46d2 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 @@ public class NotificationEntryManagerTest extends SysuiTestCase { 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 @@ public class NotificationEntryManagerTest extends SysuiTestCase { 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/collection/NotificationDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java index ab7f960d6a15..657ec61dfd11 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 @@ -653,6 +653,7 @@ public class NotificationDataTest extends SysuiTestCase { 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 @@ public class NotificationDataTest extends SysuiTestCase { 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/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java index 2f2449473e1b..219aef1ec685 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 @@ public class NotificationPanelViewTest extends SysuiTestCase { 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 5d3cdc88aa99..2623b46ba50f 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 @@ public class ScrimControllerTest extends SysuiTestCase { } @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 @@ public class ScrimControllerTest extends SysuiTestCase { 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/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java index 766ad978f475..e629a4f03586 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java @@ -27,7 +27,6 @@ import static org.mockito.Mockito.when; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; -import android.os.Looper; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; @@ -78,6 +77,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { mBluetoothControllerImpl = new BluetoothControllerImpl(mContext, mTestableLooper.getLooper(), + mTestableLooper.getLooper(), mMockBluetoothManager); } @@ -109,18 +109,13 @@ public class BluetoothControllerImplTest extends SysuiTestCase { BluetoothController.Callback callback = mock(BluetoothController.Callback.class); mBluetoothControllerImpl.addCallback(callback); - // Grab the main looper, we'll need it later. - TestableLooper mainLooper = new TestableLooper(Looper.getMainLooper()); - // Trigger the state getting. assertEquals(BluetoothDevice.BOND_NONE, mBluetoothControllerImpl.getBondState(device)); - mTestableLooper.processMessages(1); - mainLooper.processAllMessages(); + mTestableLooper.processAllMessages(); assertEquals(BluetoothDevice.BOND_BONDED, mBluetoothControllerImpl.getBondState(device)); verify(callback).onBluetoothDevicesChanged(); - mainLooper.destroy(); } @Test @@ -130,20 +125,15 @@ public class BluetoothControllerImplTest extends SysuiTestCase { BluetoothController.Callback callback = mock(BluetoothController.Callback.class); mBluetoothControllerImpl.addCallback(callback); - // Grab the main looper, we'll need it later. - TestableLooper mainLooper = new TestableLooper(Looper.getMainLooper()); - // Trigger the state getting. assertEquals(BluetoothProfile.STATE_DISCONNECTED, mBluetoothControllerImpl.getMaxConnectionState(device)); - mTestableLooper.processMessages(1); - mainLooper.processAllMessages(); + mTestableLooper.processAllMessages(); assertEquals(BluetoothProfile.STATE_CONNECTED, mBluetoothControllerImpl.getMaxConnectionState(device)); verify(callback).onBluetoothDevicesChanged(); - mainLooper.destroy(); } @Test @@ -153,19 +143,11 @@ public class BluetoothControllerImplTest extends SysuiTestCase { BluetoothController.Callback callback = mock(BluetoothController.Callback.class); mBluetoothControllerImpl.addCallback(callback); - // Grab the main looper, we'll need it later. - TestableLooper mainLooper = new TestableLooper(Looper.getMainLooper()); - - try { - // Trigger the state getting. - assertEquals(BluetoothProfile.STATE_DISCONNECTED, - mBluetoothControllerImpl.getMaxConnectionState(null)); + // Trigger the state getting. + assertEquals(BluetoothProfile.STATE_DISCONNECTED, + mBluetoothControllerImpl.getMaxConnectionState(null)); - mTestableLooper.processMessages(1); - mainLooper.processAllMessages(); - } finally { - mainLooper.destroy(); - } + mTestableLooper.processAllMessages(); } @Test @@ -217,15 +199,8 @@ public class BluetoothControllerImplTest extends SysuiTestCase { mBluetoothControllerImpl.onAclConnectionStateChanged(device, BluetoothProfile.STATE_CONNECTED); - // Grab the main looper, we'll need it later. - TestableLooper mainLooper = new TestableLooper(Looper.getMainLooper()); + mTestableLooper.processAllMessages(); - try { - mTestableLooper.processAllMessages(); - mainLooper.processAllMessages(); - } finally { - mainLooper.destroy(); - } assertTrue(mBluetoothControllerImpl.isBluetoothConnected()); verify(callback, atLeastOnce()).onBluetoothStateChange(anyBoolean()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java index 3ddfbdac6db8..aa4723acba62 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java @@ -147,7 +147,7 @@ public class NetworkControllerDataTest extends NetworkControllerBaseTest { } @Test - public void testNoInternetIcon_withoutDefaultSub() { + public void testNonDefaultSIM_showsFullSignal_connected() { setupNetworkController(); when(mMockTm.isDataCapable()).thenReturn(false); setupDefaultSignal(); @@ -158,11 +158,11 @@ public class NetworkControllerDataTest extends NetworkControllerBaseTest { // Verify that a SignalDrawable with a cut out is used to display data disabled. verifyLastMobileDataIndicators(true, DEFAULT_SIGNAL_STRENGTH, 0, true, DEFAULT_QS_SIGNAL_STRENGTH, 0, false, - false, true, NOT_DEFAULT_DATA_STRING); + false, false, NOT_DEFAULT_DATA_STRING); } @Test - public void testDataDisabledIcon_withoutDefaultSub() { + public void testNonDefaultSIM_showsFullSignal_disconnected() { setupNetworkController(); when(mMockTm.isDataCapable()).thenReturn(false); setupDefaultSignal(); @@ -173,7 +173,7 @@ public class NetworkControllerDataTest extends NetworkControllerBaseTest { // Verify that a SignalDrawable with a cut out is used to display data disabled. verifyLastMobileDataIndicators(true, DEFAULT_SIGNAL_STRENGTH, 0, true, DEFAULT_QS_SIGNAL_STRENGTH, 0, false, - false, true, NOT_DEFAULT_DATA_STRING); + false, false, NOT_DEFAULT_DATA_STRING); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java index 0c6f25786190..0d56cbe84eb0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java @@ -500,6 +500,7 @@ public class SmartReplyViewTest extends SysuiTestCase { private void setSmartActions(String[] actionTitles, boolean useDelayedOnClickListener) { mView.resetSmartSuggestions(mContainer); List<Button> actions = mView.inflateSmartActions( + getContext(), new SmartReplyView.SmartActions(createActions(actionTitles), false), mLogger, mEntry, @@ -520,6 +521,7 @@ public class SmartReplyViewTest extends SysuiTestCase { List<Button> smartSuggestions = inflateSmartReplies(choices, fromAssistant, useDelayedOnClickListener); smartSuggestions.addAll(mView.inflateSmartActions( + getContext(), new SmartReplyView.SmartActions(createActions(actionTitles), fromAssistant), mLogger, mEntry, @@ -866,7 +868,7 @@ public class SmartReplyViewTest extends SysuiTestCase { } private Button inflateActionButton(Notification.Action action) { - return SmartReplyView.inflateActionButton(mView, getContext(), 0, + return SmartReplyView.inflateActionButton(mView, getContext(), getContext(), 0, new SmartReplyView.SmartActions(Collections.singletonList(action), false), mLogger, mEntry, mHeadsUpManager, true /* useDelayedOnClickListener */); } 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 index 4a9b1b3c4006..9149599f2c7c 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.verify; 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 @@ import org.junit.runner.RunWith; @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 class AsyncSensorManagerTest extends SysuiTestCase { 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 @@ public class AsyncSensorManagerTest extends SysuiTestCase { mAsyncSensorManager.registerListener(mListener, mSensor.getSensor(), 100); mAsyncSensorManager.unregisterListener(mListener); - mAsyncSensorManager.waitUntilRequestsCompleted(); + waitUntilRequestsCompleted(); // Verify listener was unregistered. mSensor.sendProximityResult(true); @@ -85,7 +85,7 @@ public class AsyncSensorManagerTest extends SysuiTestCase { 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 @@ public class AsyncSensorManagerTest extends SysuiTestCase { 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 index 29b8ab600caf..1deb495b5250 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 class FakeSensorManager extends SensorManager { 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 @@ public class FakeSensorManager extends SensorManager { } 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 @@ public class FakeSensorManager extends SensorManager { 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 000000000000..6d13408058cc --- /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/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto index f40a1eea255b..5b826b1c551b 100644 --- a/proto/src/metrics_constants/metrics_constants.proto +++ b/proto/src/metrics_constants/metrics_constants.proto @@ -7422,6 +7422,9 @@ message MetricsEvent { // OS: Q - QPR1 ACTION_ACTIVITY_CHOOSER_PICKED_SYSTEM_TARGET = 1749; + // OPEN: Settings > System > Aware > Aware Display + // OS: Q + SETTINGS_AWARE_DISPLAY = 1750; // ---- End Q Constants, all Q constants go above this line ---- // Add new aosp constants above this line. // END OF AOSP CONSTANTS diff --git a/proto/src/wifi.proto b/proto/src/wifi.proto index 8ad24894a1b9..fbf6ca52f11c 100644 --- a/proto/src/wifi.proto +++ b/proto/src/wifi.proto @@ -1792,6 +1792,25 @@ message ExperimentValues { // 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 Kbps + optional int32 data_stall_tx_tput_thr_kbps = 6; + + // Threshold of Rx throughput below which to trigger a data stall + // measured in Kbps + optional int32 data_stall_rx_tput_thr_kbps = 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 cdb062d1963c..4f021ad3cee0 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_CLEAR_ACCE 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 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } - 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 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } - private void notifyGestureInternal(AccessibilityGestureInfo gestureInfo) { + private void notifyGestureInternal(AccessibilityGestureEvent gestureInfo) { final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); if (listener != null) { try { @@ -1469,7 +1469,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ 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 feb7329bdda6..1f11059392a1 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.FunctionalUtils.ignoreRemoteException; 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 class AccessibilityManagerService extends IAccessibilityManager.Stub } } - - 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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; } } @@ -2200,6 +2205,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } synchronized(mLock) { final UserState userState = getUserStateLocked(mCurrentUserId); + if (userState.mServiceToEnableWithShortcut == null) { + return null; + } return userState.mServiceToEnableWithShortcut.flattenToString(); } } 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 9101a01bc8fe..7e8fb295d036 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 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen /** * 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 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen 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 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen 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/EventDispatcher.java b/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java new file mode 100644 index 000000000000..dc7a9aaf966d --- /dev/null +++ b/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java @@ -0,0 +1,309 @@ +/* + * 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.accessibility.gestures; + +import static com.android.server.accessibility.gestures.TouchExplorer.DEBUG; +import static com.android.server.accessibility.gestures.TouchState.ALL_POINTER_ID_BITS; +import static com.android.server.accessibility.gestures.TouchState.MAX_POINTER_COUNT; + +import android.content.Context; +import android.util.Slog; +import android.view.MotionEvent; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; + +import com.android.server.accessibility.AccessibilityManagerService; +import com.android.server.accessibility.EventStreamTransformation; +import com.android.server.policy.WindowManagerPolicy; + +/** + * This class dispatches motion events and accessibility events relating to touch exploration and + * gesture dispatch. TouchExplorer is responsible for insuring that the receiver of motion events is + * set correctly so that events go to the right place. + */ +class EventDispatcher { + private static final String LOG_TAG = "EventDispatcher"; + + private final AccessibilityManagerService mAms; + private Context mContext; + // The receiver of motion events. + private EventStreamTransformation mReceiver; + // Keep track of which pointers sent to the system are down. + private int mInjectedPointersDown; + + // The time of the last injected down. + private long mLastInjectedDownEventTime; + + // The last injected hover event. + private MotionEvent mLastInjectedHoverEvent; + private TouchState mState; + + EventDispatcher( + Context context, + AccessibilityManagerService ams, + EventStreamTransformation receiver, + TouchState state) { + mContext = context; + mAms = ams; + mReceiver = receiver; + mState = state; + } + + public void setReceiver(EventStreamTransformation receiver) { + mReceiver = receiver; + } + + /** + * Sends an event. + * + * @param prototype The prototype from which to create the injected events. + * @param action The action of the event. + * @param pointerIdBits The bits of the pointers to send. + * @param policyFlags The policy flags associated with the event. + */ + void sendMotionEvent(MotionEvent prototype, int action, int pointerIdBits, int policyFlags) { + prototype.setAction(action); + + MotionEvent event = null; + if (pointerIdBits == ALL_POINTER_ID_BITS) { + event = prototype; + } else { + try { + event = prototype.split(pointerIdBits); + } catch (IllegalArgumentException e) { + Slog.e(LOG_TAG, "sendMotionEvent: Failed to split motion event: " + e); + return; + } + } + if (action == MotionEvent.ACTION_DOWN) { + event.setDownTime(event.getEventTime()); + } else { + event.setDownTime(getLastInjectedDownEventTime()); + } + if (DEBUG) { + Slog.d( + LOG_TAG, + "Injecting event: " + + event + + ", policyFlags=0x" + + Integer.toHexString(policyFlags)); + } + + // Make sure that the user will see the event. + policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER; + // TODO: For now pass null for the raw event since the touch + // explorer is the last event transformation and it does + // not care about the raw event. + if (mReceiver != null) { + mReceiver.onMotionEvent(event, null, policyFlags); + } else { + Slog.e(LOG_TAG, "Error sending event: no receiver specified."); + } + updateState(event); + + if (event != prototype) { + event.recycle(); + } + } + + /** + * Sends an accessibility event of the given type. + * + * @param type The event type. + */ + void sendAccessibilityEvent(int type) { + AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext); + if (accessibilityManager.isEnabled()) { + AccessibilityEvent event = AccessibilityEvent.obtain(type); + event.setWindowId(mAms.getActiveWindowId()); + accessibilityManager.sendAccessibilityEvent(event); + if (DEBUG) { + Slog.d( + LOG_TAG, + "Sending accessibility event" + AccessibilityEvent.eventTypeToString(type)); + } + } + // Todo: get rid of this and have TouchState control the sending of events rather than react + // to it. + mState.onInjectedAccessibilityEvent(type); + } + + /** + * Processes an injected {@link MotionEvent} event. + * + * @param event The event to process. + */ + void updateState(MotionEvent event) { + final int action = event.getActionMasked(); + final int pointerId = event.getPointerId(event.getActionIndex()); + final int pointerFlag = (1 << pointerId); + switch (action) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_POINTER_DOWN: + mInjectedPointersDown |= pointerFlag; + mLastInjectedDownEventTime = event.getDownTime(); + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_POINTER_UP: + mInjectedPointersDown &= ~pointerFlag; + if (mInjectedPointersDown == 0) { + mLastInjectedDownEventTime = 0; + } + break; + case MotionEvent.ACTION_HOVER_ENTER: + case MotionEvent.ACTION_HOVER_MOVE: + case MotionEvent.ACTION_HOVER_EXIT: + if (mLastInjectedHoverEvent != null) { + mLastInjectedHoverEvent.recycle(); + } + mLastInjectedHoverEvent = MotionEvent.obtain(event); + break; + } + if (DEBUG) { + Slog.i(LOG_TAG, "Injected pointer:\n" + toString()); + } + } + + /** Clears the internals state. */ + public void clear() { + mInjectedPointersDown = 0; + } + + /** @return The time of the last injected down event. */ + public long getLastInjectedDownEventTime() { + return mLastInjectedDownEventTime; + } + + /** @return The number of down pointers injected to the view hierarchy. */ + public int getInjectedPointerDownCount() { + return Integer.bitCount(mInjectedPointersDown); + } + + /** @return The bits of the injected pointers that are down. */ + public int getInjectedPointersDown() { + return mInjectedPointersDown; + } + + /** + * Whether an injected pointer is down. + * + * @param pointerId The unique pointer id. + * @return True if the pointer is down. + */ + public boolean isInjectedPointerDown(int pointerId) { + final int pointerFlag = (1 << pointerId); + return (mInjectedPointersDown & pointerFlag) != 0; + } + + /** @return The the last injected hover event. */ + public MotionEvent getLastInjectedHoverEvent() { + return mLastInjectedHoverEvent; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("========================="); + builder.append("\nDown pointers #"); + builder.append(Integer.bitCount(mInjectedPointersDown)); + builder.append(" [ "); + for (int i = 0; i < MAX_POINTER_COUNT; i++) { + if ((mInjectedPointersDown & i) != 0) { + builder.append(i); + builder.append(" "); + } + } + builder.append("]"); + builder.append("\n========================="); + return builder.toString(); + } + + /** + * Computes the action for an injected event based on a masked action and a pointer index. + * + * @param actionMasked The masked action. + * @param pointerIndex The index of the pointer which has changed. + * @return The action to be used for injection. + */ + private int computeInjectionAction(int actionMasked, int pointerIndex) { + switch (actionMasked) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_POINTER_DOWN: + // Compute the action based on how many down pointers are injected. + if (getInjectedPointerDownCount() == 0) { + return MotionEvent.ACTION_DOWN; + } else { + return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT) + | MotionEvent.ACTION_POINTER_DOWN; + } + case MotionEvent.ACTION_POINTER_UP: + // Compute the action based on how many down pointers are injected. + if (getInjectedPointerDownCount() == 1) { + return MotionEvent.ACTION_UP; + } else { + return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT) + | MotionEvent.ACTION_POINTER_UP; + } + default: + return actionMasked; + } + } + /** + * Sends down events to the view hierarchy for all pointers which are not already being + * delivered i.e. pointers that are not yet injected. + * + * @param prototype The prototype from which to create the injected events. + * @param policyFlags The policy flags associated with the event. + */ + void sendDownForAllNotInjectedPointers(MotionEvent prototype, int policyFlags) { + + // Inject the injected pointers. + int pointerIdBits = 0; + final int pointerCount = prototype.getPointerCount(); + for (int i = 0; i < pointerCount; i++) { + final int pointerId = prototype.getPointerId(i); + // Do not send event for already delivered pointers. + if (!isInjectedPointerDown(pointerId)) { + pointerIdBits |= (1 << pointerId); + final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, i); + sendMotionEvent(prototype, action, pointerIdBits, policyFlags); + } + } + } + + /** + * Sends up events to the view hierarchy for all pointers which are already being delivered i.e. + * pointers that are injected. + * + * @param prototype The prototype from which to create the injected events. + * @param policyFlags The policy flags associated with the event. + */ + void sendUpForInjectedDownPointers(MotionEvent prototype, int policyFlags) { + int pointerIdBits = 0; + final int pointerCount = prototype.getPointerCount(); + for (int i = 0; i < pointerCount; i++) { + final int pointerId = prototype.getPointerId(i); + // Skip non injected down pointers. + if (!isInjectedPointerDown(pointerId)) { + continue; + } + pointerIdBits |= (1 << pointerId); + final int action = computeInjectionAction(MotionEvent.ACTION_UP, i); + sendMotionEvent(prototype, action, pointerIdBits, 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 a338b901d24c..f4ac82157d04 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 android.view.MotionEvent.INVALID_POINTER_ID; 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; @@ -29,11 +29,11 @@ import android.view.InputDevice; import android.view.MotionEvent; import android.view.ViewConfiguration; import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import com.android.server.accessibility.AccessibilityManagerService; import com.android.server.accessibility.BaseEventStreamTransformation; +import com.android.server.accessibility.EventStreamTransformation; import com.android.server.policy.WindowManagerPolicy; import java.util.ArrayList; @@ -61,7 +61,7 @@ import java.util.List; public class TouchExplorer extends BaseEventStreamTransformation implements AccessibilityGestureDetector.Listener { - private static final boolean DEBUG = false; + static final boolean DEBUG = false; // Tag for logging received events. private static final String LOG_TAG = "TouchExplorer"; @@ -109,8 +109,7 @@ public class TouchExplorer extends BaseEventStreamTransformation // Helper class to track received pointers. private final TouchState.ReceivedPointerTracker mReceivedPointerTracker; - // Helper class to track injected pointers. - private final TouchState.InjectedPointerTracker mInjectedPointerTracker; + private final EventDispatcher mDispatcher; // Handle to the accessibility manager service. private final AccessibilityManagerService mAms; @@ -148,7 +147,7 @@ public class TouchExplorer extends BaseEventStreamTransformation mAms = service; mState = new TouchState(); mReceivedPointerTracker = mState.getReceivedPointerTracker(); - mInjectedPointerTracker = mState.getInjectedPointerTracker(); + mDispatcher = new EventDispatcher(context, mAms, super.getNext(), mState); mDetermineUserIntentTimeout = ViewConfiguration.getDoubleTapTimeout(); mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop(); mHandler = new Handler(context.getMainLooper()); @@ -197,10 +196,10 @@ public class TouchExplorer extends BaseEventStreamTransformation } else if (mState.isDragging()) { mDraggingPointerId = INVALID_POINTER_ID; // Send exit to all pointers that we have delivered. - sendUpForInjectedDownPointers(event, policyFlags); + mDispatcher.sendUpForInjectedDownPointers(event, policyFlags); } else if (mState.isDelegating()) { // Send exit to all pointers that we have delivered. - sendUpForInjectedDownPointers(event, policyFlags); + mDispatcher.sendUpForInjectedDownPointers(event, policyFlags); } else if (mState.isGestureDetecting()) { // No state specific cleanup required. } @@ -271,7 +270,8 @@ public class TouchExplorer extends BaseEventStreamTransformation if (mSendTouchExplorationEndDelayed.isPending() && eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) { mSendTouchExplorationEndDelayed.cancel(); - sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END); + mDispatcher.sendAccessibilityEvent( + AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END); } // The event for touch interaction end should be strictly after the @@ -279,7 +279,7 @@ public class TouchExplorer extends BaseEventStreamTransformation if (mSendTouchInteractionEndDelayed.isPending() && eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) { mSendTouchInteractionEndDelayed.cancel(); - sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); + mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); } super.onAccessibilityEvent(event); } @@ -318,7 +318,7 @@ public class TouchExplorer extends BaseEventStreamTransformation } // Announce the end of a new touch interaction. - sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); + mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); // Try to use the standard accessibility API to click if (!mAms.performActionOnAccessibilityFocusedItem( @@ -338,19 +338,19 @@ public class TouchExplorer extends BaseEventStreamTransformation mExitGestureDetectionModeDelayed.post(); // Send accessibility event to announce the start // of gesture recognition. - sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_START); + mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_START); return false; } @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; } @@ -371,7 +371,8 @@ public class TouchExplorer extends BaseEventStreamTransformation mSendHoverEnterAndMoveDelayed.addEvent(event); mSendHoverEnterAndMoveDelayed.forceSendAndRemove(); mSendHoverExitDelayed.cancel(); - sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags); + mDispatcher.sendMotionEvent( + event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags); return true; } } @@ -417,7 +418,7 @@ public class TouchExplorer extends BaseEventStreamTransformation if (!mGestureDetector.firstTapDetected() && mState.isClear()) { mSendTouchExplorationEndDelayed.forceSendAndRemove(); mSendTouchInteractionEndDelayed.forceSendAndRemove(); - sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_START); + mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_START); } else { // Let gesture to handle to avoid duplicated TYPE_TOUCH_INTERACTION_END event. mSendTouchInteractionEndDelayed.cancel(); @@ -536,18 +537,19 @@ public class TouchExplorer extends BaseEventStreamTransformation mState.startDragging(); mDraggingPointerId = pointerId; event.setEdgeFlags(mReceivedPointerTracker.getLastReceivedDownEdgeFlags()); - sendMotionEvent(event, MotionEvent.ACTION_DOWN, pointerIdBits, policyFlags); + mDispatcher.sendMotionEvent( + event, MotionEvent.ACTION_DOWN, pointerIdBits, policyFlags); } else { // Two pointers moving arbitrary are delegated to the view hierarchy. mState.startDelegating(); - sendDownForAllNotInjectedPointers(event, policyFlags); + mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags); } break; default: // More than two pointers are delegated to the view hierarchy. mState.startDelegating(); event = MotionEvent.obtainNoHistory(event); - sendDownForAllNotInjectedPointers(event, policyFlags); + mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags); break; } } @@ -585,7 +587,8 @@ public class TouchExplorer extends BaseEventStreamTransformation case 1: // Touch exploration. sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags); - sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags); + mDispatcher.sendMotionEvent( + event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags); break; case 2: if (mSendHoverEnterAndMoveDelayed.isPending()) { @@ -658,9 +661,10 @@ public class TouchExplorer extends BaseEventStreamTransformation // goes down => delegate the three pointers to the view hierarchy mState.startDelegating(); if (mDraggingPointerId != INVALID_POINTER_ID) { - sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags); + mDispatcher.sendMotionEvent( + event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags); } - sendDownForAllNotInjectedPointers(event, policyFlags); + mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags); } break; case MotionEvent.ACTION_MOVE: { if (mDraggingPointerId == INVALID_POINTER_ID) { @@ -672,21 +676,12 @@ public class TouchExplorer extends BaseEventStreamTransformation } break; case 2: { if (isDraggingGesture(event)) { - // Adjust event location to the middle location of the two pointers. - final float firstPtrX = event.getX(0); - final float firstPtrY = event.getY(0); - final float secondPtrX = event.getX(1); - final float secondPtrY = event.getY(1); - final int pointerIndex = event.findPointerIndex(mDraggingPointerId); - final float deltaX = - (pointerIndex == 0) ? (secondPtrX - firstPtrX) - : (firstPtrX - secondPtrX); - final float deltaY = - (pointerIndex == 0) ? (secondPtrY - firstPtrY) - : (firstPtrY - secondPtrY); - event.offsetLocation(deltaX / 2, deltaY / 2); // If still dragging send a drag event. - sendMotionEvent(event, MotionEvent.ACTION_MOVE, pointerIdBits, + adjustEventLocationForDrag(event); + mDispatcher.sendMotionEvent( + event, + MotionEvent.ACTION_MOVE, + pointerIdBits, policyFlags); } else { // The two pointers are moving either in different directions or @@ -695,20 +690,20 @@ public class TouchExplorer extends BaseEventStreamTransformation // Remove move history before send injected non-move events event = MotionEvent.obtainNoHistory(event); // Send an event to the end of the drag gesture. - sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, + mDispatcher.sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags); // Deliver all pointers to the view hierarchy. - sendDownForAllNotInjectedPointers(event, policyFlags); + mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags); } } break; default: { mState.startDelegating(); event = MotionEvent.obtainNoHistory(event); // Send an event to the end of the drag gesture. - sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, + mDispatcher.sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags); // Deliver all pointers to the view hierarchy. - sendDownForAllNotInjectedPointers(event, policyFlags); + mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags); } } } break; @@ -716,20 +711,22 @@ public class TouchExplorer extends BaseEventStreamTransformation final int pointerId = event.getPointerId(event.getActionIndex()); if (pointerId == mDraggingPointerId) { mDraggingPointerId = INVALID_POINTER_ID; - // Send an event to the end of the drag gesture. - sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags); + // Send an event to the end of the drag gesture. + mDispatcher.sendMotionEvent( + event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags); } } break; case MotionEvent.ACTION_UP: { mAms.onTouchInteractionEnd(); // Announce the end of a new touch interaction. - sendAccessibilityEvent( + mDispatcher.sendAccessibilityEvent( AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); final int pointerId = event.getPointerId(event.getActionIndex()); if (pointerId == mDraggingPointerId) { mDraggingPointerId = INVALID_POINTER_ID; // Send an event to the end of the drag gesture. - sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags); + mDispatcher.sendMotionEvent( + event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags); } } break; } @@ -751,16 +748,18 @@ public class TouchExplorer extends BaseEventStreamTransformation } case MotionEvent.ACTION_UP: { // Deliver the event. - sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags); + mDispatcher.sendMotionEvent( + event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags); // Announce the end of a the touch interaction. mAms.onTouchInteractionEnd(); - sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); + mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); } break; default: { - // Deliver the event. - sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags); + // Deliver the event. + mDispatcher.sendMotionEvent( + event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags); } } } @@ -769,57 +768,15 @@ public class TouchExplorer extends BaseEventStreamTransformation mAms.onTouchInteractionEnd(); // Announce the end of the gesture recognition. - sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END); + mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END); // Don't announce the end of a the touch interaction if users didn't lift their fingers. if (interactionEnd) { - sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); + mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); } mExitGestureDetectionModeDelayed.cancel(); } - /** - * Sends an accessibility event of the given type. - * - * @param type The event type. - */ - private void sendAccessibilityEvent(int type) { - AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext); - if (accessibilityManager.isEnabled()) { - AccessibilityEvent event = AccessibilityEvent.obtain(type); - event.setWindowId(mAms.getActiveWindowId()); - accessibilityManager.sendAccessibilityEvent(event); - if (DEBUG) { - Slog.d( - LOG_TAG, - "Sending accessibility event" + AccessibilityEvent.eventTypeToString(type)); - } - } - mState.onInjectedAccessibilityEvent(type); - } - - /** - * Sends down events to the view hierarchy for all pointers which are - * not already being delivered i.e. pointers that are not yet injected. - * - * @param prototype The prototype from which to create the injected events. - * @param policyFlags The policy flags associated with the event. - */ - private void sendDownForAllNotInjectedPointers(MotionEvent prototype, int policyFlags) { - - // Inject the injected pointers. - int pointerIdBits = 0; - final int pointerCount = prototype.getPointerCount(); - for (int i = 0; i < pointerCount; i++) { - final int pointerId = prototype.getPointerId(i); - // Do not send event for already delivered pointers. - if (!mInjectedPointerTracker.isInjectedPointerDown(pointerId)) { - pointerIdBits |= (1 << pointerId); - final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, i); - sendMotionEvent(prototype, action, pointerIdBits, policyFlags); - } - } - } /** * Sends the exit events if needed. Such events are hover exit and touch explore @@ -828,13 +785,14 @@ public class TouchExplorer extends BaseEventStreamTransformation * @param policyFlags The policy flags associated with the event. */ private void sendHoverExitAndTouchExplorationGestureEndIfNeeded(int policyFlags) { - MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent(); + MotionEvent event = mDispatcher.getLastInjectedHoverEvent(); if (event != null && event.getActionMasked() != MotionEvent.ACTION_HOVER_EXIT) { final int pointerIdBits = event.getPointerIdBits(); if (!mSendTouchExplorationEndDelayed.isPending()) { mSendTouchExplorationEndDelayed.post(); } - sendMotionEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits, policyFlags); + mDispatcher.sendMotionEvent( + event, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits, policyFlags); } } @@ -845,115 +803,14 @@ public class TouchExplorer extends BaseEventStreamTransformation * @param policyFlags The policy flags associated with the event. */ private void sendTouchExplorationGestureStartAndHoverEnterIfNeeded(int policyFlags) { - MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent(); + MotionEvent event = mDispatcher.getLastInjectedHoverEvent(); if (event != null && event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) { final int pointerIdBits = event.getPointerIdBits(); - sendMotionEvent(event, MotionEvent.ACTION_HOVER_ENTER, pointerIdBits, policyFlags); + mDispatcher.sendMotionEvent( + event, MotionEvent.ACTION_HOVER_ENTER, pointerIdBits, policyFlags); } } - /** - * Sends up events to the view hierarchy for all pointers which are - * already being delivered i.e. pointers that are injected. - * - * @param prototype The prototype from which to create the injected events. - * @param policyFlags The policy flags associated with the event. - */ - private void sendUpForInjectedDownPointers(MotionEvent prototype, int policyFlags) { - int pointerIdBits = 0; - final int pointerCount = prototype.getPointerCount(); - for (int i = 0; i < pointerCount; i++) { - final int pointerId = prototype.getPointerId(i); - // Skip non injected down pointers. - if (!mInjectedPointerTracker.isInjectedPointerDown(pointerId)) { - continue; - } - pointerIdBits |= (1 << pointerId); - final int action = computeInjectionAction(MotionEvent.ACTION_UP, i); - sendMotionEvent(prototype, action, pointerIdBits, policyFlags); - } - } - - /** - * Sends an event. - * - * @param prototype The prototype from which to create the injected events. - * @param action The action of the event. - * @param pointerIdBits The bits of the pointers to send. - * @param policyFlags The policy flags associated with the event. - */ - private void sendMotionEvent(MotionEvent prototype, int action, int pointerIdBits, - int policyFlags) { - prototype.setAction(action); - - MotionEvent event = null; - if (pointerIdBits == ALL_POINTER_ID_BITS) { - event = prototype; - } else { - try { - event = prototype.split(pointerIdBits); - } catch (IllegalArgumentException e) { - Slog.e(LOG_TAG, "sendMotionEvent: Failed to split motion event: " + e); - return; - } - } - if (action == MotionEvent.ACTION_DOWN) { - event.setDownTime(event.getEventTime()); - } else { - event.setDownTime(mInjectedPointerTracker.getLastInjectedDownEventTime()); - } - if (DEBUG) { - Slog.d(LOG_TAG, "Injecting event: " + event + ", policyFlags=0x" - + Integer.toHexString(policyFlags)); - } - - // Make sure that the user will see the event. - policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER; - // TODO: For now pass null for the raw event since the touch - // explorer is the last event transformation and it does - // not care about the raw event. - super.onMotionEvent(event, null, policyFlags); - - mInjectedPointerTracker.onMotionEvent(event); - - if (event != prototype) { - event.recycle(); - } - } - - /** - * Computes the action for an injected event based on a masked action - * and a pointer index. - * - * @param actionMasked The masked action. - * @param pointerIndex The index of the pointer which has changed. - * @return The action to be used for injection. - */ - private int computeInjectionAction(int actionMasked, int pointerIndex) { - switch (actionMasked) { - case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_POINTER_DOWN: { - // Compute the action based on how many down pointers are injected. - if (mInjectedPointerTracker.getInjectedPointerDownCount() == 0) { - return MotionEvent.ACTION_DOWN; - } else { - return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT) - | MotionEvent.ACTION_POINTER_DOWN; - } - } - case MotionEvent.ACTION_POINTER_UP: { - // Compute the action based on how many down pointers are injected. - if (mInjectedPointerTracker.getInjectedPointerDownCount() == 1) { - return MotionEvent.ACTION_UP; - } else { - return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT) - | MotionEvent.ACTION_POINTER_UP; - } - } - default: - return actionMasked; - } - } /** * Determines whether a two pointer gesture is a dragging one. @@ -978,10 +835,34 @@ public class TouchExplorer extends BaseEventStreamTransformation MAX_DRAGGING_ANGLE_COS); } + /** + * Adjust the location of an injected event when performing a drag The new location will be in + * between the two fingers touching the screen. + */ + private void adjustEventLocationForDrag(MotionEvent event) { + + final float firstPtrX = event.getX(0); + final float firstPtrY = event.getY(0); + final float secondPtrX = event.getX(1); + final float secondPtrY = event.getY(1); + final int pointerIndex = event.findPointerIndex(mDraggingPointerId); + final float deltaX = + (pointerIndex == 0) ? (secondPtrX - firstPtrX) : (firstPtrX - secondPtrX); + final float deltaY = + (pointerIndex == 0) ? (secondPtrY - firstPtrY) : (firstPtrY - secondPtrY); + event.offsetLocation(deltaX / 2, deltaY / 2); + } + public TouchState getState() { return mState; } + @Override + public void setNext(EventStreamTransformation next) { + mDispatcher.setReceiver(next); + super.setNext(next); + } + /** * Class for delayed exiting from gesture detecting mode. */ @@ -998,7 +879,7 @@ public class TouchExplorer extends BaseEventStreamTransformation @Override public void run() { // Announce the end of gesture recognition. - sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END); + mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END); clear(); } } @@ -1055,11 +936,12 @@ public class TouchExplorer extends BaseEventStreamTransformation public void run() { // Send an accessibility event to announce the touch exploration start. - sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START); + mDispatcher.sendAccessibilityEvent( + AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START); if (!mEvents.isEmpty()) { // Deliver a down event. - sendMotionEvent(mEvents.get(0), MotionEvent.ACTION_HOVER_ENTER, + mDispatcher.sendMotionEvent(mEvents.get(0), MotionEvent.ACTION_HOVER_ENTER, mPointerIdBits, mPolicyFlags); if (DEBUG) { Slog.d(LOG_TAG_SEND_HOVER_DELAYED, @@ -1069,7 +951,7 @@ public class TouchExplorer extends BaseEventStreamTransformation // Deliver move events. final int eventCount = mEvents.size(); for (int i = 1; i < eventCount; i++) { - sendMotionEvent(mEvents.get(i), MotionEvent.ACTION_HOVER_MOVE, + mDispatcher.sendMotionEvent(mEvents.get(i), MotionEvent.ACTION_HOVER_MOVE, mPointerIdBits, mPolicyFlags); if (DEBUG) { Slog.d(LOG_TAG_SEND_HOVER_DELAYED, @@ -1129,7 +1011,7 @@ public class TouchExplorer extends BaseEventStreamTransformation Slog.d(LOG_TAG_SEND_HOVER_DELAYED, "Injecting motion event:" + " ACTION_HOVER_EXIT"); } - sendMotionEvent(mPrototype, MotionEvent.ACTION_HOVER_EXIT, + mDispatcher.sendMotionEvent(mPrototype, MotionEvent.ACTION_HOVER_EXIT, mPointerIdBits, mPolicyFlags); if (!mSendTouchExplorationEndDelayed.isPending()) { mSendTouchExplorationEndDelayed.cancel(); @@ -1173,7 +1055,7 @@ public class TouchExplorer extends BaseEventStreamTransformation @Override public void run() { - sendAccessibilityEvent(mEventType); + mDispatcher.sendAccessibilityEvent(mEventType); } } diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java index 17e969a1aa49..49938fa4c6b9 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java @@ -18,6 +18,8 @@ package com.android.server.accessibility.gestures; import static android.view.MotionEvent.INVALID_POINTER_ID; +import static com.android.server.accessibility.gestures.TouchExplorer.DEBUG; + import android.annotation.IntDef; import android.util.Slog; import android.view.MotionEvent; @@ -29,14 +31,12 @@ import android.view.accessibility.AccessibilityEvent; * dispatch. */ public class TouchState { - - private static final boolean DEBUG = false; private static final String LOG_TAG = "TouchState"; // Pointer-related constants // This constant captures the current implementation detail that // pointer IDs are between 0 and 31 inclusive (subject to change). // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h) - private static final int MAX_POINTER_COUNT = 32; + static final int MAX_POINTER_COUNT = 32; // Constant referring to the ids bits of all pointers. public static final int ALL_POINTER_ID_BITS = 0xFFFFFFFF; @@ -71,13 +71,9 @@ public class TouchState { // Helper class to track received pointers. // Todo: collapse or hide this class so multiple classes don't modify it. private final ReceivedPointerTracker mReceivedPointerTracker; - // Helper class to track injected pointers. - // Todo: collapse or hide this class so multiple classes don't modify it. - private final InjectedPointerTracker mInjectedPointerTracker; public TouchState() { mReceivedPointerTracker = new ReceivedPointerTracker(); - mInjectedPointerTracker = new InjectedPointerTracker(); } /** Clears the internal shared state. */ @@ -85,16 +81,6 @@ public class TouchState { setState(STATE_CLEAR); // Reset the pointer trackers. mReceivedPointerTracker.clear(); - mInjectedPointerTracker.clear(); - } - - /** - * Updates the state in response to a hover event dispatched by TouchExplorer. - * - * @param event The event sent from TouchExplorer. - */ - public void onInjectedMotionEvent(MotionEvent event) { - mInjectedPointerTracker.onMotionEvent(event); } /** @@ -226,117 +212,10 @@ public class TouchState { } } - public InjectedPointerTracker getInjectedPointerTracker() { - return mInjectedPointerTracker; - } - public ReceivedPointerTracker getReceivedPointerTracker() { return mReceivedPointerTracker; } - /** This class tracks the up/down state of each pointer. It does not track movement. */ - class InjectedPointerTracker { - private static final String LOG_TAG_INJECTED_POINTER_TRACKER = "InjectedPointerTracker"; - - // Keep track of which pointers sent to the system are down. - private int mInjectedPointersDown; - - // The time of the last injected down. - private long mLastInjectedDownEventTime; - - // The last injected hover event. - private MotionEvent mLastInjectedHoverEvent; - - /** - * Processes an injected {@link MotionEvent} event. - * - * @param event The event to process. - */ - public void onMotionEvent(MotionEvent event) { - final int action = event.getActionMasked(); - final int pointerId = event.getPointerId(event.getActionIndex()); - final int pointerFlag = (1 << pointerId); - switch (action) { - case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_POINTER_DOWN: - mInjectedPointersDown |= pointerFlag; - mLastInjectedDownEventTime = event.getDownTime(); - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_POINTER_UP: - mInjectedPointersDown &= ~pointerFlag; - if (mInjectedPointersDown == 0) { - mLastInjectedDownEventTime = 0; - } - break; - case MotionEvent.ACTION_HOVER_ENTER: - case MotionEvent.ACTION_HOVER_MOVE: - case MotionEvent.ACTION_HOVER_EXIT: - if (mLastInjectedHoverEvent != null) { - mLastInjectedHoverEvent.recycle(); - } - mLastInjectedHoverEvent = MotionEvent.obtain(event); - break; - } - if (DEBUG) { - Slog.i(LOG_TAG_INJECTED_POINTER_TRACKER, "Injected pointer:\n" + toString()); - } - } - - /** Clears the internals state. */ - public void clear() { - mInjectedPointersDown = 0; - } - - /** @return The time of the last injected down event. */ - public long getLastInjectedDownEventTime() { - return mLastInjectedDownEventTime; - } - - /** @return The number of down pointers injected to the view hierarchy. */ - public int getInjectedPointerDownCount() { - return Integer.bitCount(mInjectedPointersDown); - } - - /** @return The bits of the injected pointers that are down. */ - public int getInjectedPointersDown() { - return mInjectedPointersDown; - } - - /** - * Whether an injected pointer is down. - * - * @param pointerId The unique pointer id. - * @return True if the pointer is down. - */ - public boolean isInjectedPointerDown(int pointerId) { - final int pointerFlag = (1 << pointerId); - return (mInjectedPointersDown & pointerFlag) != 0; - } - - /** @return The the last injected hover event. */ - public MotionEvent getLastInjectedHoverEvent() { - return mLastInjectedHoverEvent; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("========================="); - builder.append("\nDown pointers #"); - builder.append(Integer.bitCount(mInjectedPointersDown)); - builder.append(" [ "); - for (int i = 0; i < MAX_POINTER_COUNT; i++) { - if ((mInjectedPointersDown & i) != 0) { - builder.append(i); - builder.append(" "); - } - } - builder.append("]"); - builder.append("\n========================="); - return builder.toString(); - } - } /** This class tracks where and when a pointer went down. It does not track its movement. */ class ReceivedPointerTracker { private static final String LOG_TAG_RECEIVED_POINTER_TRACKER = "ReceivedPointerTracker"; diff --git a/services/core/java/com/android/server/MountServiceIdler.java b/services/core/java/com/android/server/MountServiceIdler.java index 1891ba9b1742..6bc1a570b7c0 100644 --- a/services/core/java/com/android/server/MountServiceIdler.java +++ b/services/core/java/com/android/server/MountServiceIdler.java @@ -27,6 +27,7 @@ import android.content.ComponentName; import android.content.Context; import android.os.RemoteException; import android.util.Slog; +import java.util.concurrent.TimeUnit; public class MountServiceIdler extends JobService { private static final String TAG = "MountServiceIdler"; @@ -48,7 +49,7 @@ public class MountServiceIdler extends JobService { mStarted = false; } } - // ... and try again tomorrow + // ... and try again right away or tomorrow scheduleIdlePass(MountServiceIdler.this); } }; @@ -98,24 +99,32 @@ public class MountServiceIdler extends JobService { public static void scheduleIdlePass(Context context) { JobScheduler tm = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); - Calendar calendar = tomorrowMidnight(); - final long timeToMidnight = calendar.getTimeInMillis() - System.currentTimeMillis(); + final long today3AM = offsetFromTodayMidnight(0, 3).getTimeInMillis(); + final long today4AM = offsetFromTodayMidnight(0, 4).getTimeInMillis(); + final long tomorrow3AM = offsetFromTodayMidnight(1, 3).getTimeInMillis(); + + long nextScheduleTime; + if (System.currentTimeMillis() > today3AM && System.currentTimeMillis() < today4AM) { + nextScheduleTime = TimeUnit.SECONDS.toMillis(10); + } else { + nextScheduleTime = tomorrow3AM - System.currentTimeMillis(); // 3AM tomorrow + } JobInfo.Builder builder = new JobInfo.Builder(MOUNT_JOB_ID, sIdleService); builder.setRequiresDeviceIdle(true); - builder.setRequiresCharging(true); - builder.setMinimumLatency(timeToMidnight); + builder.setRequiresBatteryNotLow(true); + builder.setMinimumLatency(nextScheduleTime); tm.schedule(builder.build()); } - private static Calendar tomorrowMidnight() { + private static Calendar offsetFromTodayMidnight(int nDays, int nHours) { Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(System.currentTimeMillis()); - calendar.set(Calendar.HOUR_OF_DAY, 3); + calendar.set(Calendar.HOUR_OF_DAY, nHours); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); - calendar.add(Calendar.DAY_OF_MONTH, 1); + calendar.add(Calendar.DAY_OF_MONTH, nDays); return calendar; } } diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java index 8c60f1aa439b..bc509561163a 100644 --- a/services/core/java/com/android/server/PackageWatchdog.java +++ b/services/core/java/com/android/server/PackageWatchdog.java @@ -812,7 +812,6 @@ public class PackageWatchdog { */ 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 1675b94292a7..e7569bee239e 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 @@ public final class ActiveServices { 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 @@ public final class ActiveServices { 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 6c42c7f2583c..3c7cb88174e1 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -5265,11 +5265,14 @@ public class ActivityManagerService extends IActivityManager.Stub // Inform checkpointing systems of success try { + // This line is needed to CTS test for the correct exception handling + // See b/138952436#comment36 for context + Slog.i(TAG, "About to commit checkpoint"); IStorageManager storageManager = PackageHelper.getStorageManager(); storageManager.commitChanges(); } catch (Exception e) { PowerManager pm = (PowerManager) - mContext.getSystemService(Context.POWER_SERVICE); + mInjector.getContext().getSystemService(Context.POWER_SERVICE); pm.reboot("Checkpoint commit failed"); } @@ -6115,10 +6118,9 @@ public class ActivityManagerService extends IActivityManager.Stub } @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 +7090,10 @@ public class ActivityManagerService extends IActivityManager.Stub } 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... diff --git a/services/core/java/com/android/server/am/MemoryStatUtil.java b/services/core/java/com/android/server/am/MemoryStatUtil.java index e73abd17cc07..fd64df9e86e0 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 @@ public final class MemoryStatUtil { 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 @@ public final class MemoryStatUtil { } /** - * 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 @@ public final class MemoryStatUtil { } /** - * 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/am/OWNERS b/services/core/java/com/android/server/am/OWNERS index 0509f9f0ec11..2f9a5c952659 100644 --- a/services/core/java/com/android/server/am/OWNERS +++ b/services/core/java/com/android/server/am/OWNERS @@ -12,6 +12,7 @@ suprabh@google.com varunshah@google.com kwekua@google.com bookatz@google.com +jji@google.com # Windows & Activities ogunwale@google.com diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 146be5aa7044..159e5b87e5a8 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -608,6 +608,10 @@ public class AppOpsService extends IAppOpsService.Stub { private void updateProxyState(long key, int proxyUid, @Nullable String proxyPackageName) { + if (proxyUid == Process.INVALID_UID) { + return; + } + if (mProxyUids == null) { mProxyUids = new LongSparseLongArray(); } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 066e765e6e30..77b3feec700e 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -155,7 +155,7 @@ import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; /** - * The implementation of the volume manager service. + * The implementation of the audio service for volume, audio focus, device management... * <p> * This implementation focuses on delivering a responsive UI. Most methods are * asynchronous to external calls. For example, the task of setting a volume diff --git a/services/core/java/com/android/server/audio/TEST_MAPPING b/services/core/java/com/android/server/audio/TEST_MAPPING new file mode 100644 index 000000000000..0d34c5372914 --- /dev/null +++ b/services/core/java/com/android/server/audio/TEST_MAPPING @@ -0,0 +1,15 @@ +{ + "presubmit": [ + { + "name": "CtsMediaTestCases", + "options": [ + { + "include-filter": "android.media.cts.AudioManagerTest" + }, + { + "include-filter": "android.media.cts.AudioFocusTest" + } + ] + } + ] +} diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 4af3627427d2..7302b985181b 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -981,10 +981,10 @@ public class BiometricService extends SystemService { mStatusBarService = mInjector.getStatusBarService(); // Cache the authenticators - for (int i = 0; i < FEATURE_ID.length; i++) { - if (hasFeature(FEATURE_ID[i])) { + for (int featureId : FEATURE_ID) { + if (hasFeature(featureId)) { Authenticator authenticator = - new Authenticator(FEATURE_ID[i], getAuthenticator(FEATURE_ID[i])); + new Authenticator(featureId, getAuthenticator(featureId)); mAuthenticators.add(authenticator); } } @@ -1005,8 +1005,6 @@ public class BiometricService extends SystemService { * and the error containing one of the {@link BiometricConstants} errors. */ private Pair<Integer, Integer> checkAndGetBiometricModality(int userId) { - int modality = TYPE_NONE; - // No biometric features, send error if (mAuthenticators.isEmpty()) { return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT); @@ -1022,10 +1020,11 @@ public class BiometricService extends SystemService { boolean hasTemplatesEnrolled = false; boolean enabledForApps = false; + int modality = TYPE_NONE; int firstHwAvailable = TYPE_NONE; - for (int i = 0; i < mAuthenticators.size(); i++) { - modality = mAuthenticators.get(i).getType(); - BiometricAuthenticator authenticator = mAuthenticators.get(i).getAuthenticator(); + for (Authenticator authenticatorWrapper : mAuthenticators) { + modality = authenticatorWrapper.getType(); + BiometricAuthenticator authenticator = authenticatorWrapper.getAuthenticator(); if (authenticator.isHardwareDetected()) { isHardwareDetected = true; if (firstHwAvailable == TYPE_NONE) { @@ -1036,9 +1035,6 @@ public class BiometricService extends SystemService { if (authenticator.hasEnrolledTemplates(userId)) { hasTemplatesEnrolled = true; if (isEnabledForApp(modality, userId)) { - // TODO(b/110907543): When face settings (and other settings) have both a - // user toggle as well as a work profile settings page, this needs to be - // updated to reflect the correct setting. enabledForApps = true; break; } @@ -1555,7 +1551,7 @@ public class BiometricService extends SystemService { } /** - * authenticate() (above) which is called from BiometricPrompt determines which + * handleAuthenticate() (above) which is called from BiometricPrompt determines which * modality/modalities to start authenticating with. authenticateInternal() should only be * used for: * 1) Preparing <Biometric>Services for authentication when BiometricPrompt#authenticate is, diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java index 09f52860e069..96af74a60b1d 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 @@ public class CameraServiceProxy extends SystemService 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 fc38735509f0..81e507cd24cf 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 @@ package com.android.server.compat; 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 @@ public class PlatformCompat extends IPlatformCompat.Stub { 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 @@ public class PlatformCompat extends IPlatformCompat.Stub { 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/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java index 7fb5b191a9b0..0bf43b6d1b9c 100644 --- a/services/core/java/com/android/server/display/color/ColorDisplayService.java +++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java @@ -609,6 +609,9 @@ public final class ColorDisplayService extends SystemService { @Override public void onAnimationEnd(Animator animator) { + Slog.d(TAG, tintController.getClass().getSimpleName() + + " Animation cancelled: " + mIsCancelled + + " to matrix: " + TintController.matrixToString(to, 16)); if (!mIsCancelled) { // Ensure final color matrix is set at the end of the animation. If the // animation is cancelled then don't set the final color matrix so the new @@ -1314,8 +1317,10 @@ public final class ColorDisplayService extends SystemService { * Reset the CCT value for the display white balance transform to its default value. */ public boolean resetDisplayWhiteBalanceColorTemperature() { - return setDisplayWhiteBalanceColorTemperature(getContext().getResources() - .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureDefault)); + int temperatureDefault = getContext().getResources() + .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureDefault); + Slog.d(TAG, "resetDisplayWhiteBalanceColorTemperature: " + temperatureDefault); + return setDisplayWhiteBalanceColorTemperature(temperatureDefault); } /** diff --git a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java index d2c6cd9f1007..3f1c222ab520 100644 --- a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java +++ b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java @@ -149,8 +149,6 @@ final class DisplayWhiteBalanceTintController extends TintController { cct = mTemperatureMax; } - Slog.d(ColorDisplayService.TAG, "setDisplayWhiteBalanceTemperatureMatrix: cct = " + cct); - synchronized (mLock) { mCurrentColorTemperature = cct; @@ -185,6 +183,9 @@ final class DisplayWhiteBalanceTintController extends TintController { java.lang.System.arraycopy(result, 3, mMatrixDisplayWhiteBalance, 4, 3); java.lang.System.arraycopy(result, 6, mMatrixDisplayWhiteBalance, 8, 3); } + + Slog.d(ColorDisplayService.TAG, "setDisplayWhiteBalanceTemperatureMatrix: cct = " + cct + + " matrix = " + matrixToString(mMatrixDisplayWhiteBalance, 16)); } @Override @@ -225,28 +226,6 @@ final class DisplayWhiteBalanceTintController extends TintController { } } - /** - * Format a given matrix into a string. - * - * @param matrix the matrix to format - * @param columns number of columns in the matrix - */ - private String matrixToString(float[] matrix, int columns) { - if (matrix == null || columns <= 0) { - Slog.e(ColorDisplayService.TAG, "Invalid arguments when formatting matrix to string"); - return ""; - } - - final StringBuilder sb = new StringBuilder(""); - for (int i = 0; i < matrix.length; i++) { - if (i % columns == 0) { - sb.append("\n "); - } - sb.append(String.format("%9.6f", matrix[i])); - } - return sb.toString(); - } - private ColorSpace.Rgb makeRgbColorSpaceFromXYZ(float[] redGreenBlueXYZ, float[] whiteXYZ) { return new ColorSpace.Rgb( "Display Color Space", diff --git a/services/core/java/com/android/server/display/color/TintController.java b/services/core/java/com/android/server/display/color/TintController.java index b291c645027a..8d8b9b2af04e 100644 --- a/services/core/java/com/android/server/display/color/TintController.java +++ b/services/core/java/com/android/server/display/color/TintController.java @@ -18,6 +18,7 @@ package com.android.server.display.color; import android.animation.ValueAnimator; import android.content.Context; +import android.util.Slog; import java.io.PrintWriter; @@ -95,4 +96,29 @@ abstract class TintController { * Returns whether or not this transform type is available on this device. */ public abstract boolean isAvailable(Context context); + + /** + * Format a given matrix into a string. + * + * @param matrix the matrix to format + * @param columns number of columns in the matrix + */ + static String matrixToString(float[] matrix, int columns) { + if (matrix == null || columns <= 0) { + Slog.e(ColorDisplayService.TAG, "Invalid arguments when formatting matrix to string," + + " matrix is null: " + (matrix == null) + + " columns: " + columns); + return ""; + } + + final StringBuilder sb = new StringBuilder(""); + for (int i = 0; i < matrix.length; i++) { + if (i % columns == 0) { + sb.append("\n "); + } + sb.append(String.format("%9.6f", matrix[i])); + } + return sb.toString(); + } + } diff --git a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java index 02ec10e2d49d..7b1f4c3222f3 100644 --- a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java +++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java @@ -95,6 +95,11 @@ public class DisplayWhiteBalanceController implements // A piecewise linear relationship between high light brightness and high light bias. private Spline.LinearSpline mHighLightAmbientBrightnessToBiasSpline; + private float mLatestAmbientColorTemperature; + private float mLatestAmbientBrightness; + private float mLatestLowLightBias; + private float mLatestHighLightBias; + /** * @param brightnessSensor * The sensor used to detect changes in the ambient brightness. @@ -348,6 +353,7 @@ public class DisplayWhiteBalanceController implements public void updateAmbientColorTemperature() { final long time = System.currentTimeMillis(); float ambientColorTemperature = mColorTemperatureFilter.getEstimate(time); + mLatestAmbientColorTemperature = ambientColorTemperature; if (mAmbientToDisplayColorTemperatureSpline != null && ambientColorTemperature != -1.0f) { ambientColorTemperature = @@ -355,6 +361,7 @@ public class DisplayWhiteBalanceController implements } float ambientBrightness = mBrightnessFilter.getEstimate(time); + mLatestAmbientBrightness = ambientBrightness; if (ambientColorTemperature != -1.0f && mLowLightAmbientBrightnessToBiasSpline != null) { @@ -362,6 +369,7 @@ public class DisplayWhiteBalanceController implements ambientColorTemperature = bias * ambientColorTemperature + (1.0f - bias) * mLowLightAmbientColorTemperature; + mLatestLowLightBias = bias; } if (ambientColorTemperature != -1.0f && mHighLightAmbientBrightnessToBiasSpline != null) { @@ -369,6 +377,7 @@ public class DisplayWhiteBalanceController implements ambientColorTemperature = (1.0f - bias) * ambientColorTemperature + bias * mHighLightAmbientColorTemperature; + mLatestHighLightBias = bias; } if (mAmbientColorTemperatureOverride != -1.0f) { @@ -426,6 +435,11 @@ public class DisplayWhiteBalanceController implements } mPendingAmbientColorTemperature = -1.0f; mAmbientColorTemperatureHistory.add(mAmbientColorTemperature); + Slog.d(TAG, "Display cct: " + mAmbientColorTemperature + + " Latest ambient cct: " + mLatestAmbientColorTemperature + + " Latest ambient lux: " + mLatestAmbientBrightness + + " Latest low light bias: " + mLatestLowLightBias + + " Latest high light bias: " + mLatestHighLightBias); mColorDisplayServiceInternal.setDisplayWhiteBalanceColorTemperature( (int) mAmbientColorTemperature); mLastAmbientColorTemperature = mAmbientColorTemperature; diff --git a/services/core/java/com/android/server/location/AbstractLocationProvider.java b/services/core/java/com/android/server/location/AbstractLocationProvider.java index b05742af04ee..ccfc98e2291b 100644 --- a/services/core/java/com/android/server/location/AbstractLocationProvider.java +++ b/services/core/java/com/android/server/location/AbstractLocationProvider.java @@ -18,6 +18,7 @@ package com.android.server.location; import android.content.Context; import android.location.Location; +import android.os.Binder; import android.os.Bundle; import android.os.WorkSource; @@ -80,7 +81,12 @@ public abstract class AbstractLocationProvider { * any thread. */ protected void setEnabled(boolean enabled) { - mLocationProviderManager.onSetEnabled(enabled); + long identity = Binder.clearCallingIdentity(); + try { + mLocationProviderManager.onSetEnabled(enabled); + } finally { + Binder.restoreCallingIdentity(identity); + } } /** @@ -88,21 +94,36 @@ public abstract class AbstractLocationProvider { * any thread. */ protected void setProperties(ProviderProperties properties) { - mLocationProviderManager.onSetProperties(properties); + long identity = Binder.clearCallingIdentity(); + try { + mLocationProviderManager.onSetProperties(properties); + } finally { + Binder.restoreCallingIdentity(identity); + } } /** * Call this method to report a new location. May be called from any thread. */ protected void reportLocation(Location location) { - mLocationProviderManager.onReportLocation(location); + long identity = Binder.clearCallingIdentity(); + try { + mLocationProviderManager.onReportLocation(location); + } finally { + Binder.restoreCallingIdentity(identity); + } } /** * Call this method to report a new location. May be called from any thread. */ protected void reportLocation(List<Location> locations) { - mLocationProviderManager.onReportLocation(locations); + long identity = Binder.clearCallingIdentity(); + try { + mLocationProviderManager.onReportLocation(locations); + } finally { + Binder.restoreCallingIdentity(identity); + } } /** diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java index 9b9f4de7a18f..bc051547a53f 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 @@ public class NotificationComparator 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 2d4c6cf70847..d480cb6e9800 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 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { } 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 @@ public class NotificationManagerService extends SystemService { } return true; } + if (oldN.hasCompletedProgress() != newN.hasCompletedProgress()) { if (DEBUG_INTERRUPTIVENESS) { Slog.v(TAG, "INTERRUPTIVENESS: " @@ -5911,6 +5914,16 @@ public class NotificationManagerService extends SystemService { } 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 @@ public class NotificationManagerService extends SystemService { } catch (Exception e) { Slog.w(TAG, "error recovering builder", e); } - return false; } @@ -6139,12 +6151,17 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { 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/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index 965ddc9f2782..f8b3fb259089 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -744,7 +744,7 @@ public final class OverlayManagerService extends SystemService { @NonNull final FileDescriptor out, @NonNull final FileDescriptor err, @NonNull final String[] args, @NonNull final ShellCallback callback, @NonNull final ResultReceiver resultReceiver) { - (new OverlayManagerShellCommand(this)).exec( + (new OverlayManagerShellCommand(getContext(), this)).exec( this, in, out, err, args, callback, resultReceiver); } diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java index 99f583978535..eb432759cb02 100644 --- a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java +++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java @@ -18,15 +18,23 @@ package com.android.server.om; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.Context; import android.content.om.IOverlayManager; import android.content.om.OverlayInfo; +import android.content.pm.PackageManager; +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.content.res.TypedArray; import android.os.RemoteException; import android.os.ShellCommand; import android.os.UserHandle; +import android.util.TypedValue; import java.io.PrintWriter; import java.util.List; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Implementation of 'cmd overlay' commands. @@ -36,9 +44,11 @@ import java.util.Map; * for a list of available commands. */ final class OverlayManagerShellCommand extends ShellCommand { + private final Context mContext; private final IOverlayManager mInterface; - OverlayManagerShellCommand(@NonNull final IOverlayManager iom) { + OverlayManagerShellCommand(@NonNull final Context ctx, @NonNull final IOverlayManager iom) { + mContext = ctx; mInterface = iom; } @@ -60,6 +70,8 @@ final class OverlayManagerShellCommand extends ShellCommand { return runEnableExclusive(); case "set-priority": return runSetPriority(); + case "lookup": + return runLookup(); default: return handleDefaultCommands(cmd); } @@ -102,6 +114,10 @@ final class OverlayManagerShellCommand extends ShellCommand { out.println(" 'lowest', change priority of PACKAGE to the lowest priority."); out.println(" If PARENT is the special keyword 'highest', change priority of"); out.println(" PACKAGE to the highest priority."); + out.println(" lookup [--verbose] PACKAGE-TO-LOAD PACKAGE:TYPE/NAME"); + out.println(" Load a package and print the value of a given resource"); + out.println(" applying the current configuration and enabled overlays."); + out.println(" For a more fine-grained alernative, use 'idmap2 lookup'."); } private int runList() throws RemoteException { @@ -253,4 +269,92 @@ final class OverlayManagerShellCommand extends ShellCommand { return mInterface.setPriority(packageName, newParentPackageName, userId) ? 0 : 1; } } + + private int runLookup() throws RemoteException { + final PrintWriter out = getOutPrintWriter(); + final PrintWriter err = getErrPrintWriter(); + + final boolean verbose = "--verbose".equals(getNextOption()); + + final String packageToLoad = getNextArgRequired(); + + final String fullyQualifiedResourceName = getNextArgRequired(); // package:type/name + final Pattern regex = Pattern.compile("(.*?):(.*?)/(.*?)"); + final Matcher matcher = regex.matcher(fullyQualifiedResourceName); + if (!matcher.matches()) { + err.println("Error: bad resource name, doesn't match package:type/name"); + return 1; + } + + final PackageManager pm = mContext.getPackageManager(); + if (pm == null) { + err.println("Error: failed to get package manager"); + return 1; + } + + final Resources res; + try { + res = pm.getResourcesForApplication(packageToLoad); + } catch (PackageManager.NameNotFoundException e) { + err.println("Error: failed to get resources for package " + packageToLoad); + return 1; + } + final AssetManager assets = res.getAssets(); + try { + assets.setResourceResolutionLoggingEnabled(true); + + // first try as non-complex type ... + try { + final TypedValue value = new TypedValue(); + res.getValue(fullyQualifiedResourceName, value, false /* resolveRefs */); + final CharSequence valueString = value.coerceToString(); + final String resolution = assets.getLastResourceResolution(); + + res.getValue(fullyQualifiedResourceName, value, true /* resolveRefs */); + final CharSequence resolvedString = value.coerceToString(); + + if (verbose) { + out.println(resolution); + } + + if (valueString.equals(resolvedString)) { + out.println(valueString); + } else { + out.println(valueString + " -> " + resolvedString); + } + return 0; + } catch (Resources.NotFoundException e) { + // this is ok, resource could still be a complex type + } + + // ... then try as complex type + try { + + final String pkg = matcher.group(1); + final String type = matcher.group(2); + final String name = matcher.group(3); + final int resid = res.getIdentifier(name, type, pkg); + if (resid == 0) { + throw new Resources.NotFoundException(); + } + final TypedArray array = res.obtainTypedArray(resid); + if (verbose) { + out.println(assets.getLastResourceResolution()); + } + TypedValue tv = new TypedValue(); + for (int i = 0; i < array.length(); i++) { + array.getValue(i, tv); + out.println(tv.coerceToString()); + } + array.recycle(); + return 0; + } catch (Resources.NotFoundException e) { + // give up + err.println("Error: failed to get the resource " + fullyQualifiedResourceName); + return 1; + } + } finally { + assets.setResourceResolutionLoggingEnabled(false); + } + } } diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java index 48678bfac86e..61ea84f9dc7f 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.Slog; 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 @@ import java.util.Set; * 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 @@ class AppsFilter { /** @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 @@ class AppsFilter { 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 @@ class AppsFilter { } /** - * 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 @@ class AppsFilter { 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 @@ class AppsFilter { 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 @@ class AppsFilter { 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 @@ class AppsFilter { return true; } } - if (!featureEnabled) { - return false; - } - 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; + + if (DEBUG_LOGGING) { + log(callingPkgSetting, targetPkgSetting, + DEBUG_ALLOW_ALL ? "ALLOWED" : "BLOCKED"); } + return !DEBUG_ALLOW_ALL; } private boolean shouldFilterApplicationInternal( @@ -425,6 +408,12 @@ class AppsFilter { 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 5eaddf9ffa05..9e04c4b69bd0 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 @@ class InstantAppRegistry { @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 58596aa0aad2..587862ad1963 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -17124,8 +17124,7 @@ public class PackageManagerService extends IPackageManager.Stub if (new File(signaturePath).exists() && !VerityUtils.hasFsverity(filePath)) { try { VerityUtils.setUpFsverity(filePath, signaturePath); - } catch (IOException | DigestException | NoSuchAlgorithmException - | SecurityException e) { + } catch (IOException e) { throw new PrepareFailure(PackageManager.INSTALL_FAILED_BAD_SIGNATURE, "Failed to enable fs-verity: " + e); } @@ -19787,7 +19786,7 @@ public class PackageManagerService extends IPackageManager.Stub public void sendSessionCommitBroadcast(PackageInstaller.SessionInfo sessionInfo, int userId) { UserManagerService ums = UserManagerService.getInstance(); - if (ums != null) { + if (ums != null && !sessionInfo.isStaged()) { final UserInfo parent = ums.getProfileParent(userId); final int launcherUid = (parent != null) ? parent.id : userId; final ComponentName launcherComponent = getDefaultHomeActivity(launcherUid); @@ -23380,11 +23379,24 @@ public class PackageManagerService extends IPackageManager.Stub } @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/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 597246884ca5..1873a4ec98d9 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -62,6 +62,7 @@ import android.os.PersistableBundle; import android.os.Process; import android.os.SELinux; import android.os.SystemClock; +import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.os.storage.StorageManager; @@ -94,6 +95,7 @@ import com.android.server.pm.permission.BasePermission; import com.android.server.pm.permission.PermissionSettings; import com.android.server.pm.permission.PermissionsState; import com.android.server.pm.permission.PermissionsState.PermissionState; +import com.android.server.utils.TimingsTraceAndSlog; import libcore.io.IoUtils; @@ -4012,8 +4014,11 @@ public final class Settings { } } - void createNewUserLI(@NonNull PackageManagerService service, @NonNull Installer installer, - int userHandle, String[] disallowedPackages) { + void createNewUserLI(@NonNull PackageManagerService service, + @NonNull Installer installer, int userHandle, String[] disallowedPackages) { + final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing", + Trace.TRACE_TAG_PACKAGE_MANAGER); + t.traceBegin("createNewUser-" + userHandle); String[] volumeUuids; String[] names; int[] appIds; @@ -4051,6 +4056,7 @@ public final class Settings { targetSdkVersions[i] = ps.pkg.applicationInfo.targetSdkVersion; } } + t.traceBegin("createAppData"); for (int i = 0; i < packagesCount; i++) { if (names[i] == null) { continue; @@ -4064,9 +4070,11 @@ public final class Settings { Slog.w(TAG, "Failed to prepare app data", e); } } + t.traceEnd(); // createAppData synchronized (mLock) { applyDefaultPreferredAppsLPw(userHandle); } + t.traceEnd(); // createNewUser } void removeUserLPw(int userId) { diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 3482e92a6fbd..1cea4ca1b945 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 @@ public class StagingManager { // 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 @@ public class StagingManager { return; } mPreRebootVerificationHandler.notifyPreRebootVerification_Apk_Complete( - originalSession); + originalSession.sessionId); }); apkSession.commit(receiver.getIntentSender(), false); return; @@ -526,7 +526,7 @@ public class StagingManager { void commitSession(@NonNull PackageInstallerSession session) { updateStoredSession(session); - mPreRebootVerificationHandler.startPreRebootVerification(session); + mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId); } @Nullable @@ -653,7 +653,7 @@ public class StagingManager { 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 @@ public class StagingManager { @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 @@ public class StagingManager { } // 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 @@ public class StagingManager { */ 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 @@ public class StagingManager { } } - notifyPreRebootVerification_Apex_Complete(session); + notifyPreRebootVerification_Apex_Complete(session.sessionId); } /** @@ -819,7 +828,7 @@ public class StagingManager { */ 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/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index dd1adb703e62..9371c4473bb3 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -2713,14 +2713,25 @@ public class UserManagerService extends IUserManager.Stub { return createUserInternalUnchecked(name, flags, parentId, disallowedPackages); } - private UserInfo createUserInternalUnchecked(String name, int flags, int parentId, - String[] disallowedPackages) { + private UserInfo createUserInternalUnchecked(@Nullable String name, int flags, + int parentId, @Nullable String[] disallowedPackages) { + TimingsTraceAndSlog t = new TimingsTraceAndSlog(); + t.traceBegin("createUser"); + UserInfo userInfo = + createUserInternalUncheckedNoTracing(name, flags, parentId, disallowedPackages, t); + t.traceEnd(); + return userInfo; + } + + private UserInfo createUserInternalUncheckedNoTracing(@Nullable String name, int flags, + int parentId, @Nullable String[] disallowedPackages, @NonNull TimingsTraceAndSlog t) { DeviceStorageMonitorInternal dsm = LocalServices .getService(DeviceStorageMonitorInternal.class); if (dsm.isMemoryLow()) { Log.w(LOG_TAG, "Cannot add user. Not enough space on disk."); return null; } + final boolean isGuest = (flags & UserInfo.FLAG_GUEST) != 0; final boolean isManagedProfile = (flags & UserInfo.FLAG_MANAGED_PROFILE) != 0; final boolean isRestricted = (flags & UserInfo.FLAG_RESTRICTED) != 0; @@ -2820,11 +2831,21 @@ public class UserManagerService extends IUserManager.Stub { } } } + + t.traceBegin("createUserKey"); final StorageManager storage = mContext.getSystemService(StorageManager.class); storage.createUserKey(userId, userInfo.serialNumber, userInfo.isEphemeral()); + t.traceEnd(); + + t.traceBegin("prepareUserData"); mUserDataPreparer.prepareUserData(userId, userInfo.serialNumber, StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE); + t.traceEnd(); + + t.traceBegin("PM.createNewUser"); mPm.createNewUser(userId, disallowedPackages); + t.traceEnd(); + userInfo.partial = false; synchronized (mPackagesLock) { writeUserLP(userData); @@ -2839,7 +2860,11 @@ public class UserManagerService extends IUserManager.Stub { synchronized (mRestrictionsLock) { mBaseUserRestrictions.append(userId, restrictions); } + + t.traceBegin("PM.onNewUserCreated"); mPm.onNewUserCreated(userId); + t.traceEnd(); + Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED); addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId); mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL, diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index e57f43685108..eb648b33c4ca 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -542,6 +542,10 @@ public final class PowerManagerService extends SystemService // True if we in the process of performing a forceSuspend private boolean mForceSuspendActive; + // Transition to Doze is in progress. We have transitioned to WAKEFULNESS_DOZING, + // but the DreamService has not yet been told to start (it's an async process). + private boolean mDozeStartInProgress; + private final class ForegroundProfileObserver extends SynchronousUserSwitchObserver { @Override public void onUserSwitching(int newUserId) throws RemoteException {} @@ -1514,6 +1518,7 @@ public final class PowerManagerService extends SystemService mLastSleepTime = eventTime; mLastSleepReason = reason; mSandmanSummoned = true; + mDozeStartInProgress = true; setWakefulnessLocked(WAKEFULNESS_DOZING, reason, eventTime); // Report the number of wake locks that will be cleared by going to sleep. @@ -1601,6 +1606,10 @@ public final class PowerManagerService extends SystemService mWakefulness = wakefulness; mWakefulnessChanging = true; mDirty |= DIRTY_WAKEFULNESS; + + // This is only valid while we are in wakefulness dozing. Set to false otherwise. + mDozeStartInProgress &= (mWakefulness == WAKEFULNESS_DOZING); + if (mNotifier != null) { mNotifier.onWakefulnessChangeStarted(wakefulness, reason, eventTime); } @@ -1631,6 +1640,9 @@ public final class PowerManagerService extends SystemService if (mWakefulness == WAKEFULNESS_DOZING && (mWakeLockSummary & WAKE_LOCK_DOZE) == 0) { return; // wait until dream has enabled dozing + } else { + // Doze wakelock acquired (doze started) or device is no longer dozing. + mDozeStartInProgress = false; } if (mWakefulness == WAKEFULNESS_DOZING || mWakefulness == WAKEFULNESS_ASLEEP) { logSleepTimeoutRecapturedLocked(); @@ -2309,6 +2321,10 @@ public final class PowerManagerService extends SystemService isDreaming = false; } + // At this point, we either attempted to start the dream or no attempt will be made, + // so stop holding the display suspend blocker for Doze. + mDozeStartInProgress = false; + // Update dream state. synchronized (mLock) { // Remember the initial battery level when the dream started. @@ -2734,6 +2750,16 @@ public final class PowerManagerService extends SystemService if (mScreenBrightnessBoostInProgress) { return true; } + + // When we transition to DOZING, we have to keep the display suspend blocker + // up until the Doze service has a change to acquire the DOZE wakelock. + // Here we wait for mWakefulnessChanging to become false since the wakefulness + // transition to DOZING isn't considered "changed" until the doze wake lock is + // acquired. + if (mWakefulness == WAKEFULNESS_DOZING && mDozeStartInProgress) { + return true; + } + // Let the system suspend if the screen is off or dozing. return false; } diff --git a/services/core/java/com/android/server/security/VerityUtils.java b/services/core/java/com/android/server/security/VerityUtils.java index b1db46fb3276..856a40f3ef12 100644 --- a/services/core/java/com/android/server/security/VerityUtils.java +++ b/services/core/java/com/android/server/security/VerityUtils.java @@ -26,27 +26,19 @@ import android.util.Slog; import android.util.apk.ApkSignatureVerifier; import android.util.apk.ByteBufferFactory; import android.util.apk.SignatureNotFoundException; -import android.util.apk.VerityBuilder; import libcore.util.HexEncoding; import java.io.File; import java.io.FileDescriptor; import java.io.IOException; -import java.io.RandomAccessFile; import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.channels.FileChannel; import java.nio.file.Files; -import java.nio.file.Path; import java.nio.file.Paths; import java.security.DigestException; -import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; -import sun.security.pkcs.PKCS7; - /** Provides fsverity related operations. */ abstract public class VerityUtils { private static final String TAG = "VerityUtils"; @@ -60,8 +52,6 @@ abstract public class VerityUtils { /** The maximum size of signature file. This is just to avoid potential abuse. */ private static final int MAX_SIGNATURE_FILE_SIZE_BYTES = 8192; - private static final int COMMON_LINUX_PAGE_SIZE_IN_BYTES = 4096; - private static final boolean DEBUG = false; /** Returns true if the given file looks like containing an fs-verity signature. */ @@ -74,42 +64,15 @@ abstract public class VerityUtils { return filePath + FSVERITY_SIGNATURE_FILE_EXTENSION; } - /** Generates Merkle tree and fs-verity metadata then enables fs-verity. */ - public static void setUpFsverity(@NonNull String filePath, String signaturePath) - throws IOException, DigestException, NoSuchAlgorithmException { - final PKCS7 pkcs7 = new PKCS7(Files.readAllBytes(Paths.get(signaturePath))); - final byte[] expectedMeasurement = pkcs7.getContentInfo().getContentBytes(); - if (DEBUG) { - Slog.d(TAG, "Enabling fs-verity with signed fs-verity measurement " - + bytesToString(expectedMeasurement)); - Slog.d(TAG, "PKCS#7 info: " + pkcs7); - } - - final TrackedBufferFactory bufferFactory = new TrackedBufferFactory(); - final byte[] actualMeasurement = generateFsverityMetadata(filePath, signaturePath, - bufferFactory); - try (RandomAccessFile raf = new RandomAccessFile(filePath, "rw")) { - FileChannel ch = raf.getChannel(); - ch.position(roundUpToNextMultiple(ch.size(), COMMON_LINUX_PAGE_SIZE_IN_BYTES)); - ByteBuffer buffer = bufferFactory.getBuffer(); - - long offset = buffer.position(); - long size = buffer.limit(); - while (offset < size) { - long s = ch.write(buffer); - offset += s; - size -= s; - } + /** Enables fs-verity for the file with a PKCS#7 detached signature file. */ + public static void setUpFsverity(@NonNull String filePath, @NonNull String signaturePath) + throws IOException { + if (Files.size(Paths.get(signaturePath)) > MAX_SIGNATURE_FILE_SIZE_BYTES) { + throw new SecurityException("Signature file is unexpectedly large: " + signaturePath); } - - if (!Arrays.equals(expectedMeasurement, actualMeasurement)) { - throw new SecurityException("fs-verity measurement mismatch: " - + bytesToString(actualMeasurement) + " != " - + bytesToString(expectedMeasurement)); - } - - // This can fail if the public key is not already in .fs-verity kernel keyring. - int errno = enableFsverityNative(filePath); + byte[] pkcs7Signature = Files.readAllBytes(Paths.get(signaturePath)); + // This will fail if the public key is not already in .fs-verity kernel keyring. + int errno = enableFsverityNative(filePath, pkcs7Signature); if (errno != 0) { throw new IOException("Failed to enable fs-verity on " + filePath + ": " + Os.strerror(errno)); @@ -131,12 +94,19 @@ abstract public class VerityUtils { return true; } + private static native int enableFsverityNative(@NonNull String filePath, + @NonNull byte[] pkcs7Signature); + private static native int measureFsverityNative(@NonNull String filePath); + /** * Generates legacy Merkle tree and fs-verity metadata with Signing Block skipped. * + * @deprecated This is only used for previous fs-verity implementation, and should never be used + * on new devices. * @return {@code SetupResult} that contains the result code, and when success, the * {@code FileDescriptor} to read all the data from. */ + @Deprecated public static SetupResult generateApkVeritySetupData(@NonNull String apkPath) { if (DEBUG) { Slog.d(TAG, "Trying to install legacy apk verity to " + apkPath); @@ -173,7 +143,10 @@ abstract public class VerityUtils { /** * {@see ApkSignatureVerifier#generateApkVerityRootHash(String)}. + * @deprecated This is only used for previous fs-verity implementation, and should never be used + * on new devices. */ + @Deprecated public static byte[] generateApkVerityRootHash(@NonNull String apkPath) throws NoSuchAlgorithmException, DigestException, IOException { return ApkSignatureVerifier.generateApkVerityRootHash(apkPath); @@ -181,104 +154,16 @@ abstract public class VerityUtils { /** * {@see ApkSignatureVerifier#getVerityRootHash(String)}. + * @deprecated This is only used for previous fs-verity implementation, and should never be used + * on new devices. */ + @Deprecated public static byte[] getVerityRootHash(@NonNull String apkPath) throws IOException, SignatureNotFoundException { return ApkSignatureVerifier.getVerityRootHash(apkPath); } /** - * Generates fs-verity metadata for {@code filePath} in the buffer created by {@code - * trackedBufferFactory}. The metadata contains the Merkle tree, fs-verity descriptor and - * extensions, including a PKCS#7 signature provided in {@code signaturePath}. - * - * <p>It is worthy to note that {@code trackedBufferFactory} generates a "tracked" {@code - * ByteBuffer}. The data will be used outside this method via the factory itself. - * - * @return fs-verity signed data (struct fsverity_digest_disk) of {@code filePath}, which - * includes SHA-256 of fs-verity descriptor and authenticated extensions. - */ - private static byte[] generateFsverityMetadata(String filePath, String signaturePath, - @NonNull ByteBufferFactory trackedBufferFactory) - throws IOException, DigestException, NoSuchAlgorithmException { - try (RandomAccessFile file = new RandomAccessFile(filePath, "r")) { - VerityBuilder.VerityResult result = VerityBuilder.generateFsVerityTree( - file, trackedBufferFactory); - - ByteBuffer buffer = result.verityData; - buffer.position(result.merkleTreeSize); - - final byte[] measurement = generateFsverityDescriptorAndMeasurement(file, - result.rootHash, signaturePath, buffer); - buffer.flip(); - return constructFsveritySignedDataNative(measurement); - } - } - - /** - * Generates fs-verity descriptor including the extensions to the {@code output} and returns the - * fs-verity measurement. - * - * @return fs-verity measurement, which is a SHA-256 of fs-verity descriptor and authenticated - * extensions. - */ - private static byte[] generateFsverityDescriptorAndMeasurement( - @NonNull RandomAccessFile file, @NonNull byte[] rootHash, - @NonNull String pkcs7SignaturePath, @NonNull ByteBuffer output) - throws IOException, NoSuchAlgorithmException, DigestException { - final short kRootHashExtensionId = 1; - final short kPkcs7SignatureExtensionId = 3; - final int origPosition = output.position(); - - // For generating fs-verity file measurement, which consists of the descriptor and - // authenticated extensions (but not unauthenticated extensions and the footer). - MessageDigest md = MessageDigest.getInstance("SHA-256"); - - // 1. Generate fs-verity descriptor. - final byte[] desc = constructFsverityDescriptorNative(file.length()); - output.put(desc); - md.update(desc); - - // 2. Generate authenticated extensions. - final byte[] authExt = - constructFsverityExtensionNative(kRootHashExtensionId, rootHash.length); - output.put(authExt); - output.put(rootHash); - md.update(authExt); - md.update(rootHash); - - // 3. Generate unauthenticated extensions. - ByteBuffer header = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN); - output.putShort((short) 1); // number of unauthenticated extensions below - output.position(output.position() + 6); - - // Generate PKCS#7 extension. NB: We do not verify agaist trusted certificate (should be - // done by the caller if needed). - Path path = Paths.get(pkcs7SignaturePath); - if (Files.size(path) > MAX_SIGNATURE_FILE_SIZE_BYTES) { - throw new IllegalArgumentException("Signature size is unexpectedly large: " - + pkcs7SignaturePath); - } - final byte[] pkcs7Signature = Files.readAllBytes(path); - output.put(constructFsverityExtensionNative(kPkcs7SignatureExtensionId, - pkcs7Signature.length)); - output.put(pkcs7Signature); - - // 4. Generate the footer. - output.put(constructFsverityFooterNative(output.position() - origPosition)); - - return md.digest(); - } - - private static native int enableFsverityNative(@NonNull String filePath); - private static native int measureFsverityNative(@NonNull String filePath); - private static native byte[] constructFsveritySignedDataNative(@NonNull byte[] measurement); - private static native byte[] constructFsverityDescriptorNative(long fileSize); - private static native byte[] constructFsverityExtensionNative(short extensionId, - int extensionDataSize); - private static native byte[] constructFsverityFooterNative(int offsetToDescriptorHead); - - /** * Returns a pair of {@code SharedMemory} and {@code Integer}. The {@code SharedMemory} contains * Merkle tree and fsverity headers for the given apk, in the form that can immediately be used * for fsverity setup. The data is aligned to the beginning of {@code SharedMemory}, and has @@ -313,6 +198,11 @@ abstract public class VerityUtils { return HexEncoding.encodeToString(bytes); } + /** + * @deprecated This is only used for previous fs-verity implementation, and should never be used + * on new devices. + */ + @Deprecated public static class SetupResult { /** Result code if verity is set up correctly. */ private static final int RESULT_OK = 1; @@ -401,30 +291,4 @@ abstract public class VerityUtils { return mBuffer == null ? -1 : mBuffer.limit(); } } - - /** A {@code ByteBufferFactory} that tracks the {@code ByteBuffer} it creates. */ - private static class TrackedBufferFactory implements ByteBufferFactory { - private ByteBuffer mBuffer; - - @Override - public ByteBuffer create(int capacity) { - if (mBuffer != null) { - throw new IllegalStateException("Multiple instantiation from this factory"); - } - mBuffer = ByteBuffer.allocate(capacity); - return mBuffer; - } - - public ByteBuffer getBuffer() { - return mBuffer; - } - } - - /** Round up the number to the next multiple of the divisor. */ - private static long roundUpToNextMultiple(long number, long divisor) { - if (number > (Long.MAX_VALUE - divisor)) { - throw new IllegalArgumentException("arithmetic overflow"); - } - return ((number + (divisor - 1)) / divisor) * divisor; - } } 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 000000000000..3076284a80ed --- /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 e92abfddac8f..19b80556a779 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.internal.util.Preconditions.checkNotNull; 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/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index 5f98d1d54a24..59f051bc76a6 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -1106,14 +1106,10 @@ final class AccessibilityController { private final Point mTempPoint = new Point(); - private final Rect mTempRect = new Rect(); - private final Region mTempRegion = new Region(); private final Region mTempRegion1 = new Region(); - private final Context mContext; - private final WindowManagerService mService; private final Handler mHandler; @@ -1127,7 +1123,6 @@ final class AccessibilityController { public WindowsForAccessibilityObserver(WindowManagerService windowManagerService, int displayId, WindowsForAccessibilityCallback callback) { - mContext = windowManagerService.mContext; mService = windowManagerService; mCallback = callback; mDisplayId = displayId; diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 7a667315c6c3..c54ccd4d6844 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -2202,7 +2202,9 @@ final class ActivityRecord extends ConfigurationContainer { stack.removeTimeoutsForActivity(this); // Clean-up activities are no longer relaunching (e.g. app process died). Notify window // manager so it can update its bookkeeping. - mAtmService.mWindowManager.notifyAppRelaunchesCleared(appToken); + if (mAppWindowToken != null) { + mAppWindowToken.clearRelaunching(); + } } /** @@ -2961,6 +2963,11 @@ final class ActivityRecord extends ConfigurationContainer { if (display != null) { display.handleActivitySizeCompatModeIfNeeded(r); } + + if (r.mAppWindowToken != null) { + r.mAppWindowToken.getDisplayContent().mUnknownAppVisibilityController + .notifyAppResumedFinished(r.mAppWindowToken); + } } /** @@ -4311,7 +4318,9 @@ final class ActivityRecord extends ConfigurationContainer { "Moving to " + (andResume ? "RESUMED" : "PAUSED") + " Relaunching " + this + " callers=" + Debug.getCallers(6)); forceNewConfig = false; - mStackSupervisor.activityRelaunchingLocked(this); + if (mAppWindowToken != null) { + mAppWindowToken.startRelaunching(); + } final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(pendingResults, pendingNewIntents, configChangeFlags, new MergedConfiguration(mAtmService.getGlobalConfiguration(), diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index 50200a7cf7bc..2ab3e01278e9 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 @@ class ActivityStack extends ConfigurationContainer { // 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,12 +4941,6 @@ class ActivityStack extends ConfigurationContainer { } } - - 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/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index 1aa1d483f707..d151f86ff810 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -2480,19 +2480,17 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { } void activityRelaunchedLocked(IBinder token) { - mWindowManager.notifyAppRelaunchingFinished(token); final ActivityRecord r = ActivityRecord.isInStackLocked(token); if (r != null) { + if (r.mAppWindowToken != null) { + r.mAppWindowToken.finishRelaunching(); + } if (r.getActivityStack().shouldSleepOrShutDownActivities()) { r.setSleeping(true, true); } } } - void activityRelaunchingLocked(ActivityRecord r) { - mWindowManager.notifyAppRelaunching(r.appToken); - } - void logStackState() { mActivityMetricsLogger.logWindowState(); } diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index b35bd9e4e81a..47be792802a0 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 @@ class ActivityStarter { 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 @@ class ActivityStarter { 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 diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 2f7acba595c2..468a13d45d55 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -47,7 +47,6 @@ import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_ import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT; import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static android.content.res.Configuration.UI_MODE_TYPE_TELEVISION; import static android.os.FactoryTest.FACTORY_TEST_HIGH_LEVEL; import static android.os.FactoryTest.FACTORY_TEST_LOW_LEVEL; import static android.os.FactoryTest.FACTORY_TEST_OFF; @@ -550,7 +549,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { /** The dimensions of the thumbnails in the Recents UI. */ private int mThumbnailWidth; private int mThumbnailHeight; - private float mFullscreenThumbnailScale; /** * Flag that indicates if multi-window is enabled. @@ -790,15 +788,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { com.android.internal.R.dimen.thumbnail_width); mThumbnailHeight = res.getDimensionPixelSize( com.android.internal.R.dimen.thumbnail_height); - - if ((globalConfig.uiMode & UI_MODE_TYPE_TELEVISION) == UI_MODE_TYPE_TELEVISION) { - mFullscreenThumbnailScale = (float) res - .getInteger(com.android.internal.R.integer.thumbnail_width_tv) / - (float) globalConfig.screenWidthDp; - } else { - mFullscreenThumbnailScale = res.getFraction( - com.android.internal.R.fraction.thumbnail_fullscreen_scale, 1, 1); - } } } @@ -1697,7 +1686,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { final long origId = Binder.clearCallingIdentity(); synchronized (mGlobalLock) { ActivityRecord.activityResumedLocked(token); - mWindowManager.notifyAppResumedFinished(token); } Binder.restoreCallingIdentity(origId); } @@ -2920,7 +2908,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { mAmInternal.enforceCallingPermission(Manifest.permission.UPDATE_LOCK_TASK_PACKAGES, "updateLockTaskPackages()"); } - synchronized (this) { + synchronized (mGlobalLock) { if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "Whitelisting " + userId + ":" + Arrays.toString(packages)); getLockTaskController().updateLockTaskPackages(userId, packages); @@ -5365,7 +5353,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { final float scaleFactor = Settings.System.getFloatForUser(mContext.getContentResolver(), FONT_SCALE, 1.0f, userId); - synchronized (this) { + synchronized (mGlobalLock) { if (getGlobalConfiguration().fontScale == scaleFactor) { return; } diff --git a/services/core/java/com/android/server/wm/AnimationAdapter.java b/services/core/java/com/android/server/wm/AnimationAdapter.java index be8a0bd7ad32..278a9ba641ab 100644 --- a/services/core/java/com/android/server/wm/AnimationAdapter.java +++ b/services/core/java/com/android/server/wm/AnimationAdapter.java @@ -16,7 +16,6 @@ package com.android.server.wm; -import android.annotation.ColorInt; import android.util.proto.ProtoOutputStream; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; @@ -41,11 +40,6 @@ interface AnimationAdapter { boolean getShowWallpaper(); /** - * @return The background color behind the animation. - */ - @ColorInt int getBackgroundColor(); - - /** * Requests to start the animation. * * @param animationLeash The surface to run the animation on. See {@link SurfaceAnimator} for an diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index 557a609dbc7b..66d52cc9bf5a 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -1999,8 +1999,8 @@ public class AppTransition implements Dump { mNextAppTransitionFutureCallback, null /* finishedCallback */, mNextAppTransitionScaleUp); mNextAppTransitionFutureCallback = null; + mService.requestTraversal(); } - mService.requestTraversal(); }); } } diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index ffd9021989b4..f647fe46f067 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -955,6 +955,9 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree updateReportedVisibilityLocked(); } + // Reset the last saved PiP snap fraction on removal. + mDisplayContent.mPinnedStackControllerLocked.resetReentrySnapFraction(mActivityComponent); + mRemovingFromDisplay = false; } @@ -1021,7 +1024,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree if (DEBUG_ADD_REMOVE) Slog.v(TAG, "notifyAppStopped: " + this); mAppStopped = true; // Reset the last saved PiP snap fraction on app stop. - mDisplayContent.mPinnedStackControllerLocked.resetReentrySnapFraction(this); + mDisplayContent.mPinnedStackControllerLocked.resetReentrySnapFraction(mActivityComponent); destroySurfaces(); // Remove any starting window that was added for this app if they are still around. removeStartingWindow(); @@ -1705,10 +1708,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree return; } - 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 + 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) @@ -1726,8 +1726,8 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree stackBounds = mTmpRect; pinnedStack.getBounds(stackBounds); } - mDisplayContent.mPinnedStackControllerLocked.saveReentrySnapFraction(this, - stackBounds); + mDisplayContent.mPinnedStackControllerLocked.saveReentrySnapFraction( + mActivityComponent, stackBounds); } } else if (shouldStartChangeTransition(prevWinMode, winMode)) { initializeChangeTransition(mTmpPrevBounds); @@ -2503,14 +2503,18 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree @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/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index f2ad56a8fdfa..63ff2ea8069c 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -583,27 +583,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } }; - private final Consumer<WindowState> mUpdateWallpaperForAnimator = w -> { - final WindowStateAnimator winAnimator = w.mWinAnimator; - if (winAnimator.mSurfaceController == null || !winAnimator.hasSurface()) { - return; - } - - // If this window is animating, ensure the animation background is set. - final AnimationAdapter anim = w.mAppToken != null - ? w.mAppToken.getAnimation() - : w.getAnimation(); - if (anim != null) { - final int color = anim.getBackgroundColor(); - if (color != 0) { - final TaskStack stack = w.getStack(); - if (stack != null) { - stack.setAnimationBackground(winAnimator, color); - } - } - } - }; - private final Consumer<WindowState> mScheduleToastTimeout = w -> { final int lostFocusUid = mTmpWindow.mOwnerUid; final Handler handler = mWmService.mH; @@ -2167,7 +2146,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * so only need to configure display. */ void setForcedDensity(int density, int userId) { - final boolean clear = density == mInitialDisplayDensity; final boolean updateCurrent = userId == UserHandle.USER_CURRENT; if (mWmService.mCurrentUserId == userId || updateCurrent) { mBaseDisplayDensity = density; @@ -2384,12 +2362,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mDisplayPolicy.switchUser(); } - private void resetAnimationBackgroundAnimator() { - for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - mTaskStackContainers.getChildAt(stackNdx).resetAnimationBackgroundAnimator(); - } - } - @Override void removeIfPossible() { if (isAnimating()) { @@ -3423,14 +3395,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo forAllWindows(mUpdateWindowsForAnimator, true /* traverseTopToBottom */); } - /** - * Updates the {@link TaskStack#setAnimationBackground} for all windows. - */ - void updateBackgroundForAnimator() { - resetAnimationBackgroundAnimator(); - forAllWindows(mUpdateWallpaperForAnimator, true /* traverseTopToBottom */); - } - boolean isInputMethodClientFocus(int uid, int pid) { final WindowState imFocus = computeImeTarget(false /* updateImeTarget */); if (imFocus == null) { @@ -3884,21 +3848,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } - private static final class ScreenshotApplicationState { - WindowState appWin; - int maxLayer; - int minLayer; - boolean screenshotReady; - - void reset(boolean screenshotReady) { - appWin = null; - maxLayer = 0; - minLayer = 0; - this.screenshotReady = screenshotReady; - minLayer = (screenshotReady) ? 0 : Integer.MAX_VALUE; - } - } - /** * Base class for any direct child window container of {@link #DisplayContent} need to inherit * from. This is mainly a pass through class that allows {@link #DisplayContent} to have diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 4dbb0092140c..10d48c4d5282 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -335,7 +335,6 @@ public class DisplayPolicy { private static final Rect sTmpDisplayCutoutSafeExceptMaybeBarsRect = new Rect(); private static final Rect sTmpRect = new Rect(); - private static final Rect sTmpDockedFrame = new Rect(); private static final Rect sTmpNavFrame = new Rect(); private static final Rect sTmpLastParentFrame = new Rect(); diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java index 120ce3eb146e..ae3b5f2f70d3 100644 --- a/services/core/java/com/android/server/wm/DockedStackDividerController.java +++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java @@ -36,7 +36,6 @@ import static com.android.server.wm.AppTransition.TOUCH_RESPONSE_INTERPOLATOR; import static com.android.server.wm.DockedStackDividerControllerProto.MINIMIZED_DOCK; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; -import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM; import android.content.Context; import android.content.res.Configuration; @@ -141,8 +140,6 @@ public class DockedStackDividerController { float mLastDividerProgress; private final DividerSnapAlgorithm[] mSnapAlgorithmForRotation = new DividerSnapAlgorithm[4]; private boolean mImeHideRequested; - private final Rect mLastDimLayerRect = new Rect(); - private float mLastDimLayerAlpha; private TaskStack mDimmedStack; DockedStackDividerController(WindowManagerService service, DisplayContent displayContent) { @@ -656,14 +653,6 @@ public class DockedStackDividerController { } /** - * @return The layer used for dimming the apps when dismissing docked/fullscreen stack. Just - * above all application surfaces. - */ - private int getResizeDimLayer() { - return (mWindow != null) ? mWindow.mLayer - 1 : LAYER_OFFSET_DIM; - } - - /** * Notifies the docked stack divider controller of a visibility change that happens without * an animation. */ diff --git a/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java b/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java index 20a13334e564..c5c236416013 100644 --- a/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java +++ b/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java @@ -16,7 +16,6 @@ package com.android.server.wm; -import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.UriGrantsManager; import android.content.ClipData; diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index 34253ed6fc8c..842686441465 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -238,11 +238,6 @@ class InsetsSourceProvider { } @Override - public int getBackgroundColor() { - return 0; - } - - @Override public void startAnimation(SurfaceControl animationLeash, Transaction t, OnAnimationFinishedCallback finishCallback) { mCapturedLeash = animationLeash; diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index d528ef6ec6a5..2b5eb3ac29fb 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -71,7 +71,6 @@ class KeyguardController { private boolean mAodShowing; private boolean mKeyguardGoingAway; private boolean mDismissalRequested; - private int[] mSecondaryDisplayIdsShowing; private int mBeforeUnoccludeTransit; private int mVisibilityTransactionDepth; private final SparseArray<KeyguardDisplayState> mDisplayStates = new SparseArray<>(); @@ -328,7 +327,7 @@ class KeyguardController { return; } - mWindowManager.onKeyguardOccludedChanged(isDisplayOccluded(DEFAULT_DISPLAY)); + mWindowManager.mPolicy.onKeyguardOccludedChangedLw(isDisplayOccluded(DEFAULT_DISPLAY)); if (isKeyguardLocked()) { mService.deferWindowLayout(); try { @@ -381,7 +380,7 @@ class KeyguardController { * @return true if Keyguard can be currently dismissed without entering credentials. */ boolean canDismissKeyguard() { - return mWindowManager.isKeyguardTrusted() + return mWindowManager.mPolicy.isKeyguardTrustedLw() || !mWindowManager.isKeyguardSecure(mService.getCurrentUserId()); } @@ -516,7 +515,8 @@ class KeyguardController { } // TODO(b/123372519): isShowingDream can only works on default display. if (mDisplayId == DEFAULT_DISPLAY) { - mOccluded |= controller.mWindowManager.isShowingDream(); + mOccluded |= mService.mRootActivityContainer.getDefaultDisplay().mDisplayContent + .getDisplayPolicy().isShowingDreamLw(); } if (lastOccluded != mOccluded) { diff --git a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java index 77a024cc2e99..e67cb6fc6963 100644 --- a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java +++ b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java @@ -49,11 +49,6 @@ class LocalAnimationAdapter implements AnimationAdapter { } @Override - public int getBackgroundColor() { - return mSpec.getBackgroundColor(); - } - - @Override public void startAnimation(SurfaceControl animationLeash, Transaction t, OnAnimationFinishedCallback finishCallback) { mAnimator.startAnimation(mSpec, animationLeash, t, @@ -100,13 +95,6 @@ class LocalAnimationAdapter implements AnimationAdapter { } /** - * @see AnimationAdapter#getBackgroundColor - */ - default int getBackgroundColor() { - return 0; - } - - /** * @see AnimationAdapter#getStatusBarTransitionsStartTime */ default long calculateStatusBarTransitionStartTime() { diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java index ef0049b068f4..8e57fec6ba46 100644 --- a/services/core/java/com/android/server/wm/PinnedStackController.java +++ b/services/core/java/com/android/server/wm/PinnedStackController.java @@ -27,6 +27,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; 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; @@ -50,7 +51,6 @@ import com.android.internal.util.Preconditions; 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 @@ class PinnedStackController { private static final String TAG = TAG_WITH_CLASS_NAME ? "PinnedStackController" : TAG_WM; - public static final float INVALID_SNAP_FRACTION = -1f; + private static final float INVALID_SNAP_FRACTION = -1f; private final WindowManagerService mService; private final DisplayContent mDisplayContent; private final Handler mHandler = UiThread.getHandler(); @@ -106,9 +106,6 @@ class PinnedStackController { 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; @@ -118,7 +115,6 @@ class PinnedStackController { 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(); @@ -136,16 +132,19 @@ class PinnedStackController { } @Override - public void setMinEdgeSize(int minEdgeSize) { - mHandler.post(() -> { - mCurrentMinSize = Math.max(mDefaultMinSize, minEdgeSize); - }); + public int getDisplayRotation() { + synchronized (mService.mGlobalLock) { + return mDisplayInfo.rotation; + } } @Override - public int getDisplayRotation() { + public void startAnimation(Rect destinationBounds, Rect sourceRectHint, + int animationDuration) { synchronized (mService.mGlobalLock) { - return mDisplayInfo.rotation; + final TaskStack pinnedStack = mDisplayContent.getPinnedStack(); + pinnedStack.animateResizePinnedStack(destinationBounds, + sourceRectHint, animationDuration, true /* fromFullscreen */); } } } @@ -188,7 +187,6 @@ class PinnedStackController { 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( @@ -216,6 +214,7 @@ class PinnedStackController { 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 @@ -238,58 +237,34 @@ class PinnedStackController { } /** - * 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 AppWindowToken token, final Rect stackBounds) { - mReentrySnapFraction = getSnapFraction(stackBounds); - mLastPipActivity = new WeakReference<>(token); + 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); + } } /** * Resets the last saved snap fraction so that the default bounds will be returned. */ - void resetReentrySnapFraction(AppWindowToken token) { - if (mLastPipActivity != null && mLastPipActivity.get() == token) { - mReentrySnapFraction = INVALID_SNAP_FRACTION; - mLastPipActivity = null; + 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); } } /** - * @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. */ - Rect getDefaultBounds(float snapFraction) { + private Rect getDefaultBounds(float snapFraction) { synchronized (mService.mGlobalLock) { final Rect insetBounds = new Rect(); getInsetBounds(insetBounds); @@ -311,13 +286,18 @@ class PinnedStackController { } } + 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) { - mDisplayInfo.copyFrom(displayInfo); + setDisplayInfo(displayInfo); notifyMovementBoundsChanged(false /* fromImeAdjustment */, false /* fromShelfAdjustment */); } @@ -335,7 +315,7 @@ class PinnedStackController { } else if (targetBounds.isEmpty()) { // The stack is null, we are just initializing the stack, so just store the display // info and ignore - mDisplayInfo.copyFrom(displayInfo); + setDisplayInfo(displayInfo); outBounds.setEmpty(); return false; } @@ -345,7 +325,8 @@ class PinnedStackController { // Calculate the snap fraction of the current stack along the old movement bounds final float snapFraction = getSnapFraction(postChangeStackBounds); - mDisplayInfo.copyFrom(displayInfo); + + setDisplayInfo(displayInfo); // Calculate the stack bounds in the new orientation to the same same fraction along the // rotated movement bounds. @@ -406,8 +387,11 @@ class PinnedStackController { 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 */); } } @@ -429,6 +413,10 @@ class PinnedStackController { 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); @@ -461,6 +449,15 @@ class PinnedStackController { } } + 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. */ @@ -497,23 +494,13 @@ class PinnedStackController { return; } try { - 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 Rect animatingBounds = new Rect(); final TaskStack pinnedStack = mDisplayContent.getPinnedStack(); if (pinnedStack != null) { pinnedStack.getAnimationOrCurrentBounds(animatingBounds); - } else { - animatingBounds.set(normalBounds); } - mPinnedStackListener.onMovementBoundsChanged(insetBounds, normalBounds, - animatingBounds, fromImeAdjustment, fromShelfAdjustment, - mDisplayInfo.rotation); + mPinnedStackListener.onMovementBoundsChanged(animatingBounds, + fromImeAdjustment, fromShelfAdjustment); } catch (RemoteException e) { Slog.e(TAG_WM, "Error delivering actions changed event.", e); } @@ -521,6 +508,30 @@ class PinnedStackController { } /** + * 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) { @@ -604,7 +615,6 @@ class PinnedStackController { 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/PointerEventDispatcher.java b/services/core/java/com/android/server/wm/PointerEventDispatcher.java index 8d08aa370b44..6b8144c69079 100644 --- a/services/core/java/com/android/server/wm/PointerEventDispatcher.java +++ b/services/core/java/com/android/server/wm/PointerEventDispatcher.java @@ -28,13 +28,11 @@ import com.android.server.UiThread; import java.util.ArrayList; public class PointerEventDispatcher extends InputEventReceiver { - private final InputChannel mInputChannel; private final ArrayList<PointerEventListener> mListeners = new ArrayList<>(); private PointerEventListener[] mListenersArray = new PointerEventListener[0]; public PointerEventDispatcher(InputChannel inputChannel) { super(inputChannel, UiThread.getHandler().getLooper()); - mInputChannel = inputChannel; } @Override @@ -94,7 +92,6 @@ public class PointerEventDispatcher extends InputEventReceiver { @Override public void dispose() { super.dispose(); - mInputChannel.dispose(); synchronized (mListeners) { mListeners.clear(); mListenersArray = null; diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index 19b5f3160837..795a2ca67ac3 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -93,6 +93,8 @@ public class RecentsAnimationController implements DeathRecipient { private IRecentsAnimationRunner mRunner; private final RecentsAnimationCallbacks mCallbacks; private final ArrayList<TaskAnimationAdapter> mPendingAnimations = new ArrayList<>(); + private final ArrayList<WallpaperAnimationAdapter> mPendingWallpaperAnimations = + new ArrayList<>(); private final int mDisplayId; private final Runnable mFailsafeRunnable = () -> cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "failSafeRunnable"); @@ -434,6 +436,13 @@ public class RecentsAnimationController implements DeathRecipient { mPendingAnimations.remove(taskAdapter); } + @VisibleForTesting + void removeWallpaperAnimation(WallpaperAnimationAdapter wallpaperAdapter) { + if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "removeWallpaperAnimation()"); + wallpaperAdapter.getLeashFinishedCallback().onAnimationFinished(wallpaperAdapter); + mPendingWallpaperAnimations.remove(wallpaperAdapter); + } + void startAnimation() { if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "startAnimation(): mPendingStart=" + mPendingStart + " mCanceled=" + mCanceled); @@ -442,25 +451,18 @@ public class RecentsAnimationController implements DeathRecipient { return; } try { - final ArrayList<RemoteAnimationTarget> appAnimations = new ArrayList<>(); - for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { - final TaskAnimationAdapter taskAdapter = mPendingAnimations.get(i); - final RemoteAnimationTarget target = taskAdapter.createRemoteAnimationApp(); - if (target != null) { - appAnimations.add(target); - } else { - removeAnimation(taskAdapter); - } - } + // Create the app targets + final RemoteAnimationTarget[] appTargets = createAppAnimations(); // Skip the animation if there is nothing to animate - if (appAnimations.isEmpty()) { + if (appTargets.length == 0) { cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "startAnimation-noAppWindows"); return; } - final RemoteAnimationTarget[] appTargets = appAnimations.toArray( - new RemoteAnimationTarget[appAnimations.size()]); + // Create the wallpaper targets + final RemoteAnimationTarget[] wallpaperTargets = createWallpaperAnimations(); + mPendingStart = false; // Perform layout if it was scheduled before to make sure that we get correct content @@ -479,7 +481,8 @@ public class RecentsAnimationController implements DeathRecipient { mService.getStableInsets(mDisplayId, mTmpRect); contentInsets = mTmpRect; } - mRunner.onAnimationStart(mController, appTargets, contentInsets, minimizedHomeBounds); + mRunner.onAnimationStart(mController, appTargets, wallpaperTargets, contentInsets, + minimizedHomeBounds); if (DEBUG_RECENTS_ANIMATIONS) { Slog.d(TAG, "startAnimation(): Notify animation start:"); for (int i = 0; i < mPendingAnimations.size(); i++) { @@ -495,6 +498,32 @@ public class RecentsAnimationController implements DeathRecipient { mService.mAtmInternal.notifyAppTransitionStarting(reasons, SystemClock.uptimeMillis()); } + private RemoteAnimationTarget[] createAppAnimations() { + final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>(); + for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { + final TaskAnimationAdapter taskAdapter = mPendingAnimations.get(i); + final RemoteAnimationTarget target = taskAdapter.createRemoteAnimationTarget(); + if (target != null) { + targets.add(target); + } else { + removeAnimation(taskAdapter); + } + } + return targets.toArray(new RemoteAnimationTarget[targets.size()]); + } + + private RemoteAnimationTarget[] createWallpaperAnimations() { + if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "createWallpaperAnimations()"); + return WallpaperAnimationAdapter.startWallpaperAnimations(mService, 0L, 0L, + adapter -> { + synchronized (mService.mGlobalLock) { + // If the wallpaper animation is canceled, continue with the recents + // animation + mPendingWallpaperAnimations.remove(adapter); + } + }, mPendingWallpaperAnimations); + } + void cancelAnimation(@ReorderMode int reorderMode, String reason) { cancelAnimation(reorderMode, false /*screenshot */, reason); } @@ -619,6 +648,11 @@ public class RecentsAnimationController implements DeathRecipient { removeAnimation(taskAdapter); } + for (int i = mPendingWallpaperAnimations.size() - 1; i >= 0; i--) { + final WallpaperAnimationAdapter wallpaperAdapter = mPendingWallpaperAnimations.get(i); + removeWallpaperAnimation(wallpaperAdapter); + } + // Clear any pending failsafe runnables mService.mH.removeCallbacks(mFailsafeRunnable); mDisplayContent.mAppTransition.unregisterListener(mAppTransitionListener); @@ -747,6 +781,15 @@ public class RecentsAnimationController implements DeathRecipient { return false; } + boolean isAnimatingWallpaper(WallpaperWindowToken token) { + for (int i = mPendingWallpaperAnimations.size() - 1; i >= 0; i--) { + if (token == mPendingWallpaperAnimations.get(i).getToken()) { + return true; + } + } + return false; + } + private boolean isAnimatingApp(AppWindowToken appToken) { for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { final Task task = mPendingAnimations.get(i).mTask; @@ -784,7 +827,7 @@ public class RecentsAnimationController implements DeathRecipient { mBounds.set(container.getDisplayedBounds()); } - RemoteAnimationTarget createRemoteAnimationApp() { + RemoteAnimationTarget createRemoteAnimationTarget() { final AppWindowToken topApp = mTask.getTopVisibleAppToken(); final WindowState mainWindow = topApp != null ? topApp.findMainWindow() @@ -811,11 +854,6 @@ public class RecentsAnimationController implements DeathRecipient { } @Override - public int getBackgroundColor() { - return 0; - } - - @Override public void startAnimation(SurfaceControl animationLeash, Transaction t, OnAnimationFinishedCallback finishCallback) { // Restore z-layering, position and stack crop until client has a chance to modify it. @@ -830,6 +868,7 @@ public class RecentsAnimationController implements DeathRecipient { @Override public void onAnimationCancelled(SurfaceControl animationLeash) { + // Cancel the animation immediately if any single task animator is canceled cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "taskAnimationAdapterCanceled"); } diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java index 7448e007f2c5..87bda4a545e1 100644 --- a/services/core/java/com/android/server/wm/RemoteAnimationController.java +++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java @@ -58,6 +58,8 @@ class RemoteAnimationController implements DeathRecipient { private final WindowManagerService mService; private final RemoteAnimationAdapter mRemoteAnimationAdapter; private final ArrayList<RemoteAnimationRecord> mPendingAnimations = new ArrayList<>(); + private final ArrayList<WallpaperAnimationAdapter> mPendingWallpaperAnimations = + new ArrayList<>(); private final Rect mTmpRect = new Rect(); private final Handler mHandler; private final Runnable mTimeoutRunnable = () -> cancelAnimation("timeoutRunnable"); @@ -110,16 +112,21 @@ class RemoteAnimationController implements DeathRecipient { (long) (TIMEOUT_MS * mService.getCurrentAnimatorScale())); mFinishedCallback = new FinishedCallback(this); - final RemoteAnimationTarget[] animations = createAnimations(); - if (animations.length == 0) { + // Create the app targets + final RemoteAnimationTarget[] appTargets = createAppAnimations(); + if (appTargets.length == 0) { if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "goodToGo(): No apps to animate"); onAnimationFinished(); return; } + + // Create the remote wallpaper animation targets (if any) + final RemoteAnimationTarget[] wallpaperTargets = createWallpaperAnimations(); mService.mAnimator.addAfterPrepareSurfacesRunnable(() -> { try { linkToDeathOfRunner(); - mRemoteAnimationAdapter.getRunner().onAnimationStart(animations, mFinishedCallback); + mRemoteAnimationAdapter.getRunner().onAnimationStart(appTargets, wallpaperTargets, + mFinishedCallback); } catch (RemoteException e) { Slog.e(TAG, "Failed to start remote animation", e); onAnimationFinished(); @@ -155,8 +162,8 @@ class RemoteAnimationController implements DeathRecipient { Slog.i(TAG, sw.toString()); } - private RemoteAnimationTarget[] createAnimations() { - if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "createAnimations()"); + private RemoteAnimationTarget[] createAppAnimations() { + if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "createAppAnimations()"); final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>(); for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { final RemoteAnimationRecord wrappers = mPendingAnimations.get(i); @@ -186,6 +193,19 @@ class RemoteAnimationController implements DeathRecipient { return targets.toArray(new RemoteAnimationTarget[targets.size()]); } + private RemoteAnimationTarget[] createWallpaperAnimations() { + if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "createWallpaperAnimations()"); + return WallpaperAnimationAdapter.startWallpaperAnimations(mService, + mRemoteAnimationAdapter.getDuration(), + mRemoteAnimationAdapter.getStatusBarTransitionDelay(), + adapter -> { + synchronized (mService.mGlobalLock) { + // If the wallpaper animation is canceled, continue with the app animation + mPendingWallpaperAnimations.remove(adapter); + } + }, mPendingWallpaperAnimations); + } + private void onAnimationFinished() { if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "onAnimationFinished(): mPendingAnimations=" + mPendingAnimations.size()); @@ -207,7 +227,15 @@ class RemoteAnimationController implements DeathRecipient { adapters.mThumbnailAdapter.mCapturedFinishCallback .onAnimationFinished(adapters.mThumbnailAdapter); } - if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\t" + adapters.mAppWindowToken); + mPendingAnimations.remove(i); + if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\tapp=" + adapters.mAppWindowToken); + } + + for (int i = mPendingWallpaperAnimations.size() - 1; i >= 0; i--) { + final WallpaperAnimationAdapter adapter = mPendingWallpaperAnimations.get(i); + adapter.getLeashFinishedCallback().onAnimationFinished(adapter); + mPendingWallpaperAnimations.remove(i); + if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\twallpaper=" + adapter.getToken()); } } catch (Exception e) { Slog.e(TAG, "Failed to finish remote animation", e); @@ -390,11 +418,6 @@ class RemoteAnimationController implements DeathRecipient { } @Override - public int getBackgroundColor() { - return 0; - } - - @Override public void startAnimation(SurfaceControl animationLeash, Transaction t, OnAnimationFinishedCallback finishCallback) { if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "startAnimation"); @@ -424,10 +447,7 @@ class RemoteAnimationController implements DeathRecipient { mPendingAnimations.remove(mRecord); } if (mPendingAnimations.isEmpty()) { - mHandler.removeCallbacks(mTimeoutRunnable); - releaseFinishedCallback(); - invokeAnimationCancelled(); - setRunningRemoteAnimation(false); + cancelAnimation("allAppAnimationsCanceled"); } } diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java index 4b2d4ce3d799..734f2248d131 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 @@ class RootActivityContainer extends ConfigurationContainer /** 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 @@ class RootActivityContainer extends ConfigurationContainer */ 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 @@ class RootActivityContainer extends ConfigurationContainer } } finally { mStackSupervisor.getKeyguardController().endActivityVisibilityUpdate(); + mInEnsureActivitiesVisible = false; } } @@ -959,10 +969,6 @@ class RootActivityContainer extends ConfigurationContainer // 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 @@ -1001,9 +1007,14 @@ class RootActivityContainer extends ConfigurationContainer mService.continueWindowLayout(); } - stack.animateResizePinnedStack(sourceHintBounds, destBounds, -1 /* animationDuration */, - true /* fromFullscreen */); + // 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 */); + // 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 diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index d0b6fc890ca0..4365d0325545 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -472,8 +472,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> final int count = mChildren.size(); for (int i = 0; i < count; ++i) { - final DisplayContent dc = mChildren.get(i); - final int pendingChanges = animator.getPendingLayoutChanges(dc.getDisplayId()); + final int pendingChanges = mChildren.get(i).pendingLayoutChanges; if ((pendingChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0) { animator.mBulkUpdateParams |= SET_WALLPAPER_ACTION_PENDING; } diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java index cd211a28a908..ba728ba18d57 100644 --- a/services/core/java/com/android/server/wm/SurfaceAnimator.java +++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java @@ -54,6 +54,7 @@ class SurfaceAnimator { final Animatable mAnimatable; private final OnAnimationFinishedCallback mInnerAnimationFinishedCallback; @VisibleForTesting + @Nullable final Runnable mAnimationFinishedCallback; private boolean mAnimationStartDelayed; @@ -262,7 +263,7 @@ class SurfaceAnimator { if (!mAnimationStartDelayed && forwardCancel) { animation.onAnimationCancelled(leash); } - if (!restarting) { + if (!restarting && mAnimationFinishedCallback != null) { mAnimationFinishedCallback.run(); } } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 3a2eb57f1d80..85ba80602bdb 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -85,8 +85,6 @@ class Task extends WindowContainer<AppWindowToken> implements ConfigurationConta private Rect mTmpRect = new Rect(); // For handling display rotations. private Rect mTmpRect2 = new Rect(); - // For retrieving dim bounds - private Rect mTmpRect3 = new Rect(); // Resize mode of the task. See {@link ActivityInfo#resizeMode} private int mResizeMode; @@ -613,6 +611,9 @@ class Task extends WindowContainer<AppWindowToken> implements ConfigurationConta @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/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java index 8b0b6ce8ce32..42866f97b4b8 100644 --- a/services/core/java/com/android/server/wm/TaskPositioner.java +++ b/services/core/java/com/android/server/wm/TaskPositioner.java @@ -64,10 +64,6 @@ class TaskPositioner implements IBinder.DeathRecipient { private static Factory sFactory; - // The margin the pointer position has to be within the side of the screen to be - // considered at the side of the screen. - static final int SIDE_MARGIN_DIP = 100; - @IntDef(flag = true, value = { CTRL_NONE, @@ -101,7 +97,6 @@ class TaskPositioner implements IBinder.DeathRecipient { private DisplayContent mDisplayContent; private final DisplayMetrics mDisplayMetrics = new DisplayMetrics(); private Rect mTmpRect = new Rect(); - private int mSideMargin; private int mMinVisibleWidth; private int mMinVisibleHeight; @@ -309,7 +304,6 @@ class TaskPositioner implements IBinder.DeathRecipient { // Notify InputMonitor to take mDragWindowHandle. mDisplayContent.getInputMonitor().updateInputWindowsLw(true /*force*/); - mSideMargin = dipToPixel(SIDE_MARGIN_DIP, mDisplayMetrics); mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, mDisplayMetrics); mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, mDisplayMetrics); display.getRealSize(mMaxVisibleSize); @@ -488,12 +482,6 @@ class TaskPositioner implements IBinder.DeathRecipient { int right = mWindowOriginalBounds.right; int bottom = mWindowOriginalBounds.bottom; - // The aspect which we have to respect. Note that if the orientation does not need to be - // preserved the aspect will be calculated as 1.0 which neutralizes the following - // computations. - final float minAspect = !mPreserveOrientation - ? 1.0f - : (mStartOrientationWasLandscape ? MIN_ASPECT : (1.0f / MIN_ASPECT)); // Calculate the resulting width and height of the drag operation. int width = right - left; int height = bottom - top; diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java index 4b3691c88a06..0ea108e30280 100644 --- a/services/core/java/com/android/server/wm/TaskRecord.java +++ b/services/core/java/com/android/server/wm/TaskRecord.java @@ -1085,8 +1085,8 @@ class TaskRecord extends ConfigurationContainer { clearRootProcess(); - // TODO: Use window container controller once tasks are better synced between AM and WM - mService.mWindowManager.notifyTaskRemovedFromRecents(taskId, userId); + mService.mWindowManager.mTaskSnapshotController.notifyTaskRemovedFromRecents( + taskId, userId); } void setTaskToAffiliateWith(TaskRecord taskToAffiliateWith) { diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java index 239bd004705f..10d8328a7535 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -45,7 +45,6 @@ import static com.android.server.wm.StackProto.ADJUSTED_FOR_IME; import static com.android.server.wm.StackProto.ADJUST_DIVIDER_AMOUNT; import static com.android.server.wm.StackProto.ADJUST_IME_AMOUNT; import static com.android.server.wm.StackProto.ANIMATING_BOUNDS; -import static com.android.server.wm.StackProto.ANIMATION_BACKGROUND_SURFACE_IS_DIMMING; import static com.android.server.wm.StackProto.BOUNDS; import static com.android.server.wm.StackProto.DEFER_REMOVAL; import static com.android.server.wm.StackProto.FILLS_PARENT; @@ -111,12 +110,6 @@ public class TaskStack extends WindowContainer<Task> implements */ private final Rect mFullyAdjustedImeBounds = new Rect(); - private SurfaceControl mAnimationBackgroundSurface; - private boolean mAnimationBackgroundSurfaceIsShown = false; - - /** The particular window with an Animation with non-zero background color. */ - private WindowStateAnimator mAnimationBackgroundAnimator; - /** Application tokens that are exiting, but still on screen for animations. */ final AppTokenList mExitingAppTokens = new AppTokenList(); final AppTokenList mTmpAppTokens = new AppTokenList(); @@ -230,39 +223,6 @@ public class TaskStack extends WindowContainer<Task> implements } } - private void updateAnimationBackgroundBounds() { - if (mAnimationBackgroundSurface == null) { - return; - } - getRawBounds(mTmpRect); - final Rect stackBounds = getBounds(); - getPendingTransaction() - .setWindowCrop(mAnimationBackgroundSurface, mTmpRect.width(), mTmpRect.height()) - .setPosition(mAnimationBackgroundSurface, mTmpRect.left - stackBounds.left, - mTmpRect.top - stackBounds.top); - scheduleAnimation(); - } - - private void hideAnimationSurface() { - if (mAnimationBackgroundSurface == null) { - return; - } - getPendingTransaction().hide(mAnimationBackgroundSurface); - mAnimationBackgroundSurfaceIsShown = false; - scheduleAnimation(); - } - - private void showAnimationSurface(float alpha) { - if (mAnimationBackgroundSurface == null) { - return; - } - getPendingTransaction().setLayer(mAnimationBackgroundSurface, Integer.MIN_VALUE) - .setAlpha(mAnimationBackgroundSurface, alpha) - .show(mAnimationBackgroundSurface); - mAnimationBackgroundSurfaceIsShown = true; - scheduleAnimation(); - } - @Override public int setBounds(Rect bounds) { return setBounds(getRequestedOverrideBounds(), bounds); @@ -275,10 +235,6 @@ public class TaskStack extends WindowContainer<Task> implements final int result = super.setBounds(bounds); - if (getParent() != null) { - updateAnimationBackgroundBounds(); - } - updateAdjustedBounds(); updateSurfaceBounds(); @@ -738,7 +694,6 @@ public class TaskStack extends WindowContainer<Task> implements // surface position. updateSurfaceSize(getPendingTransaction()); final int windowingMode = getWindowingMode(); - final boolean isAlwaysOnTop = isAlwaysOnTop(); if (mDisplayContent == null) { return; @@ -822,11 +777,6 @@ public class TaskStack extends WindowContainer<Task> implements super.onDisplayChanged(dc); updateSurfaceBounds(); - if (mAnimationBackgroundSurface == null) { - mAnimationBackgroundSurface = makeChildSurface(null).setColorLayer() - .setName("animation background stackId=" + mStackId) - .build(); - } } /** @@ -1008,27 +958,10 @@ public class TaskStack extends WindowContainer<Task> implements EventLog.writeEvent(EventLogTags.WM_STACK_REMOVED, mStackId); - if (mAnimationBackgroundSurface != null) { - mWmService.mTransactionFactory.get().remove(mAnimationBackgroundSurface).apply(); - mAnimationBackgroundSurface = null; - } - mDisplayContent = null; mWmService.mWindowPlacerLocked.requestTraversal(); } - void resetAnimationBackgroundAnimator() { - mAnimationBackgroundAnimator = null; - hideAnimationSurface(); - } - - void setAnimationBackground(WindowStateAnimator winAnimator, int color) { - if (mAnimationBackgroundAnimator == null) { - mAnimationBackgroundAnimator = winAnimator; - showAnimationSurface(((color >> 24) & 0xff) / 255f); - } - } - // TODO: Should each user have there own stacks? @Override void switchUser() { @@ -1365,7 +1298,6 @@ public class TaskStack extends WindowContainer<Task> implements } proto.write(FILLS_PARENT, matchParentBounds()); getRawBounds().writeToProto(proto, BOUNDS); - proto.write(ANIMATION_BACKGROUND_SURFACE_IS_DIMMING, mAnimationBackgroundSurfaceIsShown); proto.write(DEFER_REMOVAL, mDeferRemoval); proto.write(MINIMIZE_AMOUNT, mMinimizeAmount); proto.write(ADJUSTED_FOR_IME, mAdjustedForIme); @@ -1395,9 +1327,6 @@ public class TaskStack extends WindowContainer<Task> implements for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; taskNdx--) { mChildren.get(taskNdx).dump(pw, prefix + " ", dumpAll); } - if (mAnimationBackgroundSurfaceIsShown) { - pw.println(prefix + "mWindowAnimationBackgroundSurface is shown"); - } if (!mExitingAppTokens.isEmpty()) { pw.println(); pw.println(" Exiting application tokens:"); @@ -1661,40 +1590,6 @@ public class TaskStack extends WindowContainer<Task> implements } /** - * @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, @@ -1771,6 +1666,11 @@ public class TaskStack extends WindowContainer<Task> implements return; } + final DisplayContent displayContent = getDisplayContent(); + if (displayContent == null) { + return; + } + if (!inPinnedWindowingMode()) { return; } @@ -1781,13 +1681,10 @@ public class TaskStack extends WindowContainer<Task> implements if (Float.compare(aspectRatio, pinnedStackController.getAspectRatio()) == 0) { return; } - getAnimationOrCurrentBounds(mTmpFromBounds); - mTmpToBounds.set(mTmpFromBounds); - getPictureInPictureBounds(aspectRatio, mTmpToBounds); - if (!mTmpToBounds.equals(mTmpFromBounds)) { - animateResizePinnedStack(mTmpToBounds, null /* sourceHintBounds */, - -1 /* duration */, false /* fromFullscreen */); - } + + // 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. pinnedStackController.setAspectRatio( pinnedStackController.isValidPictureInPictureAspectRatio(aspectRatio) ? aspectRatio : -1f); diff --git a/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java b/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java new file mode 100644 index 000000000000..895350b43eeb --- /dev/null +++ b/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.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.wm; + +import static com.android.server.wm.AnimationAdapterProto.REMOTE; +import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET; +import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_RECENTS_ANIMATIONS; +import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_REMOTE_ANIMATIONS; + +import android.graphics.Point; +import android.os.SystemClock; +import android.util.Slog; +import android.util.proto.ProtoOutputStream; +import android.view.RemoteAnimationTarget; +import android.view.SurfaceControl; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.function.Consumer; + +/** + * An animation adapter for wallpaper windows. + */ +class WallpaperAnimationAdapter implements AnimationAdapter { + private static final String TAG = "WallpaperAnimationAdapter"; + + private final WallpaperWindowToken mWallpaperToken; + private SurfaceControl mCapturedLeash; + private SurfaceAnimator.OnAnimationFinishedCallback mCapturedLeashFinishCallback; + + private long mDurationHint; + private long mStatusBarTransitionDelay; + + private Consumer<WallpaperAnimationAdapter> mAnimationCanceledRunnable; + private RemoteAnimationTarget mTarget; + + WallpaperAnimationAdapter(WallpaperWindowToken wallpaperToken, + long durationHint, long statusBarTransitionDelay, + Consumer<WallpaperAnimationAdapter> animationCanceledRunnable) { + mWallpaperToken = wallpaperToken; + mDurationHint = durationHint; + mStatusBarTransitionDelay = statusBarTransitionDelay; + mAnimationCanceledRunnable = animationCanceledRunnable; + } + + /** + * Creates and starts remote animations for all the visible wallpaper windows. + * + * @return RemoteAnimationTarget[] targets for all the visible wallpaper windows + */ + public static RemoteAnimationTarget[] startWallpaperAnimations(WindowManagerService service, + long durationHint, long statusBarTransitionDelay, + Consumer<WallpaperAnimationAdapter> animationCanceledRunnable, + ArrayList<WallpaperAnimationAdapter> adaptersOut) { + final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>(); + service.mRoot.forAllWallpaperWindows(wallpaperWindow -> { + if (!wallpaperWindow.getDisplayContent().mWallpaperController.isWallpaperVisible()) { + if (DEBUG_REMOTE_ANIMATIONS || DEBUG_RECENTS_ANIMATIONS) { + Slog.d(TAG, "\tNot visible=" + wallpaperWindow); + } + return; + } + + if (DEBUG_REMOTE_ANIMATIONS || DEBUG_RECENTS_ANIMATIONS) { + Slog.d(TAG, "\tvisible=" + wallpaperWindow); + } + final WallpaperAnimationAdapter wallpaperAdapter = new WallpaperAnimationAdapter( + wallpaperWindow, durationHint, statusBarTransitionDelay, + animationCanceledRunnable); + wallpaperWindow.startAnimation(wallpaperWindow.getPendingTransaction(), + wallpaperAdapter, false /* hidden */); + targets.add(wallpaperAdapter.createRemoteAnimationTarget()); + adaptersOut.add(wallpaperAdapter); + }); + return targets.toArray(new RemoteAnimationTarget[targets.size()]); + } + + /** + * Create a remote animation target for this animation adapter. + */ + RemoteAnimationTarget createRemoteAnimationTarget() { + mTarget = new RemoteAnimationTarget(-1, -1, getLeash(), false, null, null, + mWallpaperToken.getPrefixOrderIndex(), new Point(), null, + mWallpaperToken.getWindowConfiguration(), true, null, null); + return mTarget; + } + + /** + * @return the leash for this animation (only valid after the wallpaper window surface animation + * has started). + */ + SurfaceControl getLeash() { + return mCapturedLeash; + } + + /** + * @return the callback to call to clean up when the animation has finished. + */ + SurfaceAnimator.OnAnimationFinishedCallback getLeashFinishedCallback() { + return mCapturedLeashFinishCallback; + } + + /** + * @return the wallpaper window + */ + WallpaperWindowToken getToken() { + return mWallpaperToken; + } + + @Override + public boolean getShowWallpaper() { + // Not used + return false; + } + + @Override + public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t, + SurfaceAnimator.OnAnimationFinishedCallback finishCallback) { + if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "startAnimation"); + + // Restore z-layering until client has a chance to modify it. + t.setLayer(animationLeash, mWallpaperToken.getPrefixOrderIndex()); + mCapturedLeash = animationLeash; + mCapturedLeashFinishCallback = finishCallback; + } + + @Override + public void onAnimationCancelled(SurfaceControl animationLeash) { + if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "onAnimationCancelled"); + mAnimationCanceledRunnable.accept(this); + } + + @Override + public long getDurationHint() { + return mDurationHint; + } + + @Override + public long getStatusBarTransitionsStartTime() { + return SystemClock.uptimeMillis() + mStatusBarTransitionDelay; + } + + @Override + public void dump(PrintWriter pw, String prefix) { + pw.print(prefix); + pw.print("token="); + pw.println(mWallpaperToken); + if (mTarget != null) { + pw.print(prefix); + pw.println("Target:"); + mTarget.dump(pw, prefix + " "); + } else { + pw.print(prefix); + pw.println("Target: null"); + } + } + + @Override + public void writeToProto(ProtoOutputStream proto) { + final long token = proto.start(REMOTE); + if (mTarget != null) { + mTarget.writeToProto(proto, TARGET); + } + proto.end(token); + } +} diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 614727263469..13902eedbfba 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -110,7 +110,6 @@ class WallpaperController { private final FindWallpaperTargetResult mFindResults = new FindWallpaperTargetResult(); private final ToBooleanFunction<WindowState> mFindWallpaperTargetFunction = w -> { - final WindowAnimator winAnimator = mService.mAnimator; if ((w.mAttrs.type == TYPE_WALLPAPER)) { if (mFindResults.topWallpaper == null || mFindResults.resetTopWallpaper) { mFindResults.setTopWallpaper(w); diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java index e15b783b5606..528cece9a78b 100644 --- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java +++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java @@ -17,6 +17,7 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; + import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -29,6 +30,8 @@ import android.util.Slog; import android.view.DisplayInfo; import android.view.animation.Animation; +import java.util.function.Consumer; + /** * A token that represents a set of wallpaper windows. */ @@ -153,6 +156,11 @@ class WallpaperWindowToken extends WindowToken { } @Override + void forAllWallpaperWindows(Consumer<WallpaperWindowToken> callback) { + callback.accept(this); + } + + @Override public String toString() { if (stringName == null) { StringBuilder sb = new StringBuilder(); diff --git a/services/core/java/com/android/server/wm/WindowAnimationSpec.java b/services/core/java/com/android/server/wm/WindowAnimationSpec.java index d8ebd84b3e73..7c183a8bf739 100644 --- a/services/core/java/com/android/server/wm/WindowAnimationSpec.java +++ b/services/core/java/com/android/server/wm/WindowAnimationSpec.java @@ -19,7 +19,6 @@ package com.android.server.wm; import static com.android.server.wm.AnimationAdapter.STATUS_BAR_TRANSITION_DURATION; import static com.android.server.wm.AnimationSpecProto.WINDOW; import static com.android.server.wm.WindowAnimationSpecProto.ANIMATION; -import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM; import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE; import android.graphics.Point; @@ -81,11 +80,6 @@ public class WindowAnimationSpec implements AnimationSpec { } @Override - public int getBackgroundColor() { - return mAnimation.getBackgroundColor(); - } - - @Override public long getDuration() { return mAnimation.computeDurationHint(); } diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index 4fce46b9dfaf..c7916e829349 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -180,7 +180,6 @@ public class WindowAnimator { // Update animations of all applications, including those // associated with exiting/removed apps dc.updateWindowsForAnimator(); - dc.updateBackgroundForAnimator(); dc.prepareSurfaces(); } @@ -306,24 +305,6 @@ public class WindowAnimator { } } - int getPendingLayoutChanges(final int displayId) { - if (displayId < 0) { - return 0; - } - final DisplayContent displayContent = mService.mRoot.getDisplayContent(displayId); - return (displayContent != null) ? displayContent.pendingLayoutChanges : 0; - } - - void setPendingLayoutChanges(final int displayId, final int changes) { - if (displayId < 0) { - return; - } - final DisplayContent displayContent = mService.mRoot.getDisplayContent(displayId); - if (displayContent != null) { - displayContent.pendingLayoutChanges |= changes; - } - } - private DisplayContentsAnimator getDisplayContentsAnimatorLocked(int displayId) { if (displayId < 0) { return null; diff --git a/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java b/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java index 775d5b2fb79a..d5d4e085b7d2 100644 --- a/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java +++ b/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java @@ -70,11 +70,6 @@ public class WindowChangeAnimationSpec implements AnimationSpec { } @Override - public int getBackgroundColor() { - return 0; - } - - @Override public long getDuration() { return mAnimation.getDuration(); } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index e280a663b7f5..586375f9d714 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -895,6 +895,12 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } } + void forAllWallpaperWindows(Consumer<WallpaperWindowToken> callback) { + for (int i = mChildren.size() - 1; i >= 0; --i) { + mChildren.get(i).forAllWallpaperWindows(callback); + } + } + /** * For all tasks at or below this container call the callback. * diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 607a013abc51..14214b4be7d1 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -313,11 +313,6 @@ public class WindowManagerService extends IWindowManager.Stub static final int WINDOW_LAYER_MULTIPLIER = 5; /** - * Dim surface layer is immediately below target window. - */ - static final int LAYER_OFFSET_DIM = 1; - - /** * Animation thumbnail is as far as possible below the window above * the thumbnail (or in other words as far as possible above the window * below it). @@ -367,14 +362,28 @@ public class WindowManagerService extends IWindowManager.Stub private static final String DENSITY_OVERRIDE = "ro.config.density_override"; private static final String SIZE_OVERRIDE = "ro.config.size_override"; - private static final int MAX_SCREENSHOT_RETRIES = 3; - private static final String PROPERTY_EMULATOR_CIRCULAR = "ro.emulator.circular"; // Used to indicate that if there is already a transition set, it should be preserved when // 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}) @@ -2048,13 +2057,11 @@ public class WindowManagerService extends IWindowManager.Stub final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); long origId = Binder.clearCallingIdentity(); - final int displayId; synchronized (mGlobalLock) { final WindowState win = windowForClientLocked(session, client, false); if (win == null) { return 0; } - displayId = win.getDisplayId(); final DisplayContent displayContent = win.getDisplayContent(); final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy(); @@ -2632,16 +2639,14 @@ public class WindowManagerService extends IWindowManager.Stub getDefaultDisplayContentLocked().executeAppTransition(); } - public void initializeRecentsAnimation(int targetActivityType, + void initializeRecentsAnimation(int targetActivityType, IRecentsAnimationRunner recentsAnimationRunner, RecentsAnimationController.RecentsAnimationCallbacks callbacks, int displayId, SparseBooleanArray recentTaskIds) { - synchronized (mGlobalLock) { - mRecentsAnimationController = new RecentsAnimationController(this, - recentsAnimationRunner, callbacks, displayId); - mRoot.getDisplayContent(displayId).mAppTransition.updateBooster(); - mRecentsAnimationController.initialize(targetActivityType, recentTaskIds); - } + mRecentsAnimationController = new RecentsAnimationController(this, recentsAnimationRunner, + callbacks, displayId); + mRoot.getDisplayContent(displayId).mAppTransition.updateBooster(); + mRecentsAnimationController.initialize(targetActivityType, recentTaskIds); } @VisibleForTesting @@ -2787,12 +2792,6 @@ public class WindowManagerService extends IWindowManager.Stub mAtmInternal.notifyKeyguardFlagsChanged(callback, displayId); } - public boolean isKeyguardTrusted() { - synchronized (mGlobalLock) { - return mPolicy.isKeyguardTrustedLw(); - } - } - public void setKeyguardGoingAway(boolean keyguardGoingAway) { synchronized (mGlobalLock) { mKeyguardGoingAway = keyguardGoingAway; @@ -2938,13 +2937,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - public boolean isShowingDream() { - synchronized (mGlobalLock) { - // TODO(b/123372519): Fix this when dream can be shown on non-default display. - return getDefaultDisplayContentLocked().getDisplayPolicy().isShowingDreamLw(); - } - } - @Override public void dismissKeyguard(IKeyguardDismissCallback callback, CharSequence message) { if (!checkCallingPermission(permission.CONTROL_KEYGUARD, "dismissKeyguard")) { @@ -2955,12 +2947,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - public void onKeyguardOccludedChanged(boolean occluded) { - synchronized (mGlobalLock) { - mPolicy.onKeyguardOccludedChangedLw(occluded); - } - } - @Override public void setSwitchingUser(boolean switching) { if (!checkCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, @@ -5331,9 +5317,7 @@ public class WindowManagerService extends IWindowManager.Stub } void requestTraversal() { - synchronized (mGlobalLock) { - mWindowPlacerLocked.requestTraversal(); - } + mWindowPlacerLocked.requestTraversal(); } /** Note that Locked in this case is on mLayoutToAnim */ @@ -5801,55 +5785,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - public void notifyAppRelaunching(IBinder token) { - synchronized (mGlobalLock) { - final AppWindowToken appWindow = mRoot.getAppWindowToken(token); - if (appWindow != null) { - appWindow.startRelaunching(); - } - } - } - - public void notifyAppRelaunchingFinished(IBinder token) { - synchronized (mGlobalLock) { - final AppWindowToken appWindow = mRoot.getAppWindowToken(token); - if (appWindow != null) { - appWindow.finishRelaunching(); - } - } - } - - public void notifyAppRelaunchesCleared(IBinder token) { - synchronized (mGlobalLock) { - final AppWindowToken appWindow = mRoot.getAppWindowToken(token); - if (appWindow != null) { - appWindow.clearRelaunching(); - } - } - } - - public void notifyAppResumedFinished(IBinder token) { - synchronized (mGlobalLock) { - final AppWindowToken appWindow = mRoot.getAppWindowToken(token); - if (appWindow != null) { - appWindow.getDisplayContent().mUnknownAppVisibilityController - .notifyAppResumedFinished(appWindow); - } - } - } - - /** - * Called when a task has been removed from the recent tasks list. - * <p> - * Note: This doesn't go through {@link TaskWindowContainerController} yet as the window - * container may not exist when this happens. - */ - public void notifyTaskRemovedFromRecents(int taskId, int userId) { - synchronized (mGlobalLock) { - mTaskSnapshotController.notifyTaskRemovedFromRecents(taskId, userId); - } - } - private void dumpPolicyLocked(PrintWriter pw, String[] args, boolean dumpAll) { pw.println("WINDOW MANAGER POLICY STATE (dumpsys window policy)"); mPolicy.dump(" ", pw, args); @@ -6359,16 +6294,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - public void onDisplayChanged(int displayId) { - synchronized (mGlobalLock) { - final DisplayContent displayContent = mRoot.getDisplayContent(displayId); - if (displayContent != null) { - displayContent.updateDisplayInfo(); - } - mWindowPlacerLocked.requestTraversal(); - } - } - @Override public Object getWindowManagerLock() { return mGlobalLock; @@ -6379,21 +6304,18 @@ public class WindowManagerService extends IWindowManager.Stub * a window. * @param token Application token for which the activity will be relaunched. */ - public void setWillReplaceWindow(IBinder token, boolean animate) { - synchronized (mGlobalLock) { - final AppWindowToken appWindowToken = mRoot.getAppWindowToken(token); - if (appWindowToken == null) { - Slog.w(TAG_WM, "Attempted to set replacing window on non-existing app token " - + token); - return; - } - if (!appWindowToken.hasContentToDisplay()) { - Slog.w(TAG_WM, "Attempted to set replacing window on app token with no content" - + token); - return; - } - appWindowToken.setWillReplaceWindows(animate); + void setWillReplaceWindow(IBinder token, boolean animate) { + final AppWindowToken appWindowToken = mRoot.getAppWindowToken(token); + if (appWindowToken == null) { + Slog.w(TAG_WM, "Attempted to set replacing window on non-existing app token " + token); + return; + } + if (!appWindowToken.hasContentToDisplay()) { + Slog.w(TAG_WM, "Attempted to set replacing window on app token with no content" + + token); + return; } + appWindowToken.setWillReplaceWindows(animate); } /** @@ -6441,19 +6363,17 @@ public class WindowManagerService extends IWindowManager.Stub * @param token Application token for the activity whose window might be replaced. * @param replacing Whether the window is being replaced or not. */ - public void scheduleClearWillReplaceWindows(IBinder token, boolean replacing) { - synchronized (mGlobalLock) { - final AppWindowToken appWindowToken = mRoot.getAppWindowToken(token); - if (appWindowToken == null) { - Slog.w(TAG_WM, "Attempted to reset replacing window on non-existing app token " - + token); - return; - } - if (replacing) { - scheduleWindowReplacementTimeouts(appWindowToken); - } else { - appWindowToken.clearWillReplaceWindows(); - } + void scheduleClearWillReplaceWindows(IBinder token, boolean replacing) { + final AppWindowToken appWindowToken = mRoot.getAppWindowToken(token); + if (appWindowToken == null) { + Slog.w(TAG_WM, "Attempted to reset replacing window on non-existing app token " + + token); + return; + } + if (replacing) { + scheduleWindowReplacementTimeouts(appWindowToken); + } else { + appWindowToken.clearWillReplaceWindows(); } } @@ -6475,11 +6395,9 @@ public class WindowManagerService extends IWindowManager.Stub } } - public void setDockedStackResizing(boolean resizing) { - synchronized (mGlobalLock) { - getDefaultDisplayContentLocked().getDockedDividerController().setResizing(resizing); - requestTraversal(); - } + void setDockedStackResizing(boolean resizing) { + getDefaultDisplayContentLocked().getDockedDividerController().setResizing(resizing); + requestTraversal(); } @Override @@ -7024,7 +6942,9 @@ public class WindowManagerService extends IWindowManager.Stub private final class LocalService extends WindowManagerInternal { @Override public void requestTraversalFromDisplayManager() { - requestTraversal(); + synchronized (mGlobalLock) { + requestTraversal(); + } } @Override diff --git a/services/core/java/com/android/server/wm/WindowProcessListener.java b/services/core/java/com/android/server/wm/WindowProcessListener.java index 23d7a6a9d293..1dade1519fdb 100644 --- a/services/core/java/com/android/server/wm/WindowProcessListener.java +++ b/services/core/java/com/android/server/wm/WindowProcessListener.java @@ -17,7 +17,6 @@ package com.android.server.wm; import android.util.proto.ProtoOutputStream; -import android.view.IRemoteAnimationRunner; import android.view.RemoteAnimationAdapter; /** diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 99ae18d67be5..501a93ef6645 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -520,11 +520,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP /** When true this window can be displayed on screens owther than mOwnerUid's */ private boolean mShowToOwnerOnly; - // Whether the window was visible when we set the app to invisible last time. WM uses - // this as a hint to restore the surface (if available) for early animation next time - // the app is brought visible. - private boolean mWasVisibleBeforeClientHidden; - // This window will be replaced due to relaunch. This allows window manager // to differentiate between simple removal of a window and replacement. In the latter case it // will preserve the old window until the new one is drawn. @@ -2013,8 +2008,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // Visibility of the removed window. Will be used later to update orientation later on. boolean wasVisible = false; - final int displayId = getDisplayId(); - // First, see if we need to run an animation. If we do, we have to hold off on removing the // window until the animation is done. If the display is frozen, just remove immediately, // since the animation wouldn't be seen. diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index ef1d110c9617..c676e723de71 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -271,20 +271,17 @@ class WindowStateAnimator { if (mAttrType == LayoutParams.TYPE_STATUS_BAR && mWin.isVisibleByPolicy()) { // Upon completion of a not-visible to visible status bar animation a relayout is // required. - if (displayContent != null) { - displayContent.setLayoutNeeded(); - } + displayContent.setLayoutNeeded(); } mWin.onExitAnimationDone(); - final int displayId = mWin.getDisplayId(); - int pendingLayoutChanges = FINISH_LAYOUT_REDO_ANIM; + displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_ANIM; if (displayContent.mWallpaperController.isWallpaperTarget(mWin)) { - pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; + displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; } - mAnimator.setPendingLayoutChanges(displayId, pendingLayoutChanges); - if (DEBUG_LAYOUT_REPEATS) + if (DEBUG_LAYOUT_REPEATS) { mService.mWindowPlacerLocked.debugLayoutRepeats( - "WindowStateAnimator", mAnimator.getPendingLayoutChanges(displayId)); + "WindowStateAnimator", displayContent.pendingLayoutChanges); + } if (mWin.mAppToken != null) { mWin.mAppToken.updateReportedVisibilityLocked(); @@ -429,10 +426,6 @@ class WindowStateAnimator { } } - private int getLayerStack() { - return mWin.getDisplayContent().getDisplay().getLayerStack(); - } - void resetDrawState() { mDrawState = DRAW_PENDING; @@ -1072,8 +1065,7 @@ class WindowStateAnimator { if (mSurfaceResized) { mReportSurfaceResized = true; - mAnimator.setPendingLayoutChanges(w.getDisplayId(), - FINISH_LAYOUT_REDO_WALLPAPER); + mWin.getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; } } @@ -1168,16 +1160,16 @@ class WindowStateAnimator { if (mIsWallpaper) { w.dispatchWallpaperVisibility(true); } - if (!w.getDisplayContent().getLastHasContent()) { + final DisplayContent displayContent = w.getDisplayContent(); + if (!displayContent.getLastHasContent()) { // This draw means the difference between unique content and mirroring. // Run another pass through performLayout to set mHasContent in the // LogicalDisplay. - mAnimator.setPendingLayoutChanges(w.getDisplayId(), - FINISH_LAYOUT_REDO_ANIM); + displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_ANIM; if (DEBUG_LAYOUT_REPEATS) { mService.mWindowPlacerLocked.debugLayoutRepeats( "showSurfaceRobustlyLocked " + w, - mAnimator.getPendingLayoutChanges(w.getDisplayId())); + displayContent.pendingLayoutChanges); } } } else { diff --git a/services/core/jni/com_android_server_security_VerityUtils.cpp b/services/core/jni/com_android_server_security_VerityUtils.cpp index 6cd9f2c718ee..9ceb7706628a 100644 --- a/services/core/jni/com_android_server_security_VerityUtils.cpp +++ b/services/core/jni/com_android_server_security_VerityUtils.cpp @@ -36,61 +36,33 @@ #include <linux/fsverity.h> #else -// Before fs-verity is upstreamed, use the current snapshot for development. -// https://git.kernel.org/pub/scm/linux/kernel/git/ebiggers/linux.git/tree/include/uapi/linux/fsverity.h?h=fsverity - #include <linux/limits.h> #include <linux/ioctl.h> #include <linux/types.h> +#define FS_VERITY_HASH_ALG_SHA256 1 + +struct fsverity_enable_arg { + __u32 version; + __u32 hash_algorithm; + __u32 block_size; + __u32 salt_size; + __u64 salt_ptr; + __u32 sig_size; + __u32 __reserved1; + __u64 sig_ptr; + __u64 __reserved2[11]; +}; + struct fsverity_digest { __u16 digest_algorithm; __u16 digest_size; /* input/output */ __u8 digest[]; }; -#define FS_IOC_ENABLE_VERITY _IO('f', 133) +#define FS_IOC_ENABLE_VERITY _IOW('f', 133, struct fsverity_enable_arg) #define FS_IOC_MEASURE_VERITY _IOWR('f', 134, struct fsverity_digest) -#define FS_VERITY_MAGIC "FSVerity" - -#define FS_VERITY_ALG_SHA256 1 - -struct fsverity_descriptor { - __u8 magic[8]; /* must be FS_VERITY_MAGIC */ - __u8 major_version; /* must be 1 */ - __u8 minor_version; /* must be 0 */ - __u8 log_data_blocksize;/* log2(data-bytes-per-hash), e.g. 12 for 4KB */ - __u8 log_tree_blocksize;/* log2(tree-bytes-per-hash), e.g. 12 for 4KB */ - __le16 data_algorithm; /* hash algorithm for data blocks */ - __le16 tree_algorithm; /* hash algorithm for tree blocks */ - __le32 flags; /* flags */ - __le32 __reserved1; /* must be 0 */ - __le64 orig_file_size; /* size of the original file data */ - __le16 auth_ext_count; /* number of authenticated extensions */ - __u8 __reserved2[30]; /* must be 0 */ -}; - -#define FS_VERITY_EXT_ROOT_HASH 1 -#define FS_VERITY_EXT_PKCS7_SIGNATURE 3 - -struct fsverity_extension { - __le32 length; - __le16 type; /* Type of this extension (see codes above) */ - __le16 __reserved; /* Reserved, must be 0 */ -}; - -struct fsverity_digest_disk { - __le16 digest_algorithm; - __le16 digest_size; - __u8 digest[]; -}; - -struct fsverity_footer { - __le32 desc_reverse_offset; /* distance to fsverity_descriptor */ - __u8 magic[8]; /* FS_VERITY_MAGIC */ -} __packed; - #endif const int kSha256Bytes = 32; @@ -99,52 +71,24 @@ namespace android { namespace { -class JavaByteArrayHolder { - public: - JavaByteArrayHolder(const JavaByteArrayHolder &other) = delete; - JavaByteArrayHolder(JavaByteArrayHolder &&other) - : mEnv(other.mEnv), mBytes(other.mBytes), mElements(other.mElements) { - other.mElements = nullptr; - } - - static JavaByteArrayHolder newArray(JNIEnv* env, jsize size) { - return JavaByteArrayHolder(env, size); - } - - jbyte* getRaw() { - return mElements; - } - - jbyteArray release() { - mEnv->ReleaseByteArrayElements(mBytes, mElements, 0); - mElements = nullptr; - return mBytes; - } - - ~JavaByteArrayHolder() { - LOG_ALWAYS_FATAL_IF(mElements != nullptr, "Elements are not released"); - } - - private: - JavaByteArrayHolder(JNIEnv* env, jsize size) { - mEnv = env; - mBytes = mEnv->NewByteArray(size); - mElements = mEnv->GetByteArrayElements(mBytes, nullptr); - memset(mElements, 0, size); - } - - JNIEnv* mEnv; - jbyteArray mBytes; - jbyte* mElements; -}; - -int enableFsverity(JNIEnv* env, jobject /* clazz */, jstring filePath) { +int enableFsverity(JNIEnv* env, jobject /* clazz */, jstring filePath, jbyteArray signature) { const char* path = env->GetStringUTFChars(filePath, nullptr); ::android::base::unique_fd rfd(open(path, O_RDONLY | O_CLOEXEC)); + env->ReleaseStringUTFChars(filePath, path); if (rfd.get() < 0) { return errno; } - if (ioctl(rfd.get(), FS_IOC_ENABLE_VERITY, nullptr) < 0) { + + fsverity_enable_arg arg = {}; + arg.version = 1; + arg.hash_algorithm = FS_VERITY_HASH_ALG_SHA256; + arg.block_size = 4096; + arg.salt_size = 0; + arg.salt_ptr = reinterpret_cast<uintptr_t>(nullptr); + arg.sig_size = env->GetArrayLength(signature); + arg.sig_ptr = reinterpret_cast<uintptr_t>(signature); + + if (ioctl(rfd.get(), FS_IOC_ENABLE_VERITY, &arg) < 0) { return errno; } return 0; @@ -159,6 +103,7 @@ int measureFsverity(JNIEnv* env, jobject /* clazz */, jstring filePath) { const char* path = env->GetStringUTFChars(filePath, nullptr); ::android::base::unique_fd rfd(open(path, O_RDONLY | O_CLOEXEC)); + env->ReleaseStringUTFChars(filePath, path); if (rfd.get() < 0) { return errno; } @@ -168,71 +113,9 @@ int measureFsverity(JNIEnv* env, jobject /* clazz */, jstring filePath) { return 0; } -jbyteArray constructFsveritySignedData(JNIEnv* env, jobject /* clazz */, jbyteArray digest) { - auto raii = JavaByteArrayHolder::newArray(env, sizeof(fsverity_digest_disk) + kSha256Bytes); - fsverity_digest_disk* data = reinterpret_cast<fsverity_digest_disk*>(raii.getRaw()); - - data->digest_algorithm = FS_VERITY_ALG_SHA256; - data->digest_size = kSha256Bytes; - if (env->GetArrayLength(digest) != kSha256Bytes) { - jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", "Invalid hash size of %d", - env->GetArrayLength(digest)); - return 0; - } - const jbyte* src = env->GetByteArrayElements(digest, nullptr); - memcpy(data->digest, src, kSha256Bytes); - - return raii.release(); -} - - -jbyteArray constructFsverityDescriptor(JNIEnv* env, jobject /* clazz */, jlong fileSize) { - auto raii = JavaByteArrayHolder::newArray(env, sizeof(fsverity_descriptor)); - fsverity_descriptor* desc = reinterpret_cast<fsverity_descriptor*>(raii.getRaw()); - - memcpy(desc->magic, FS_VERITY_MAGIC, sizeof(desc->magic)); - desc->major_version = 1; - desc->minor_version = 0; - desc->log_data_blocksize = 12; - desc->log_tree_blocksize = 12; - desc->data_algorithm = FS_VERITY_ALG_SHA256; - desc->tree_algorithm = FS_VERITY_ALG_SHA256; - desc->flags = 0; - desc->orig_file_size = fileSize; - desc->auth_ext_count = 1; - - return raii.release(); -} - -jbyteArray constructFsverityExtension(JNIEnv* env, jobject /* clazz */, jshort extensionId, - jint extensionDataSize) { - auto raii = JavaByteArrayHolder::newArray(env, sizeof(fsverity_extension)); - fsverity_extension* ext = reinterpret_cast<fsverity_extension*>(raii.getRaw()); - - ext->length = sizeof(fsverity_extension) + extensionDataSize; - ext->type = extensionId; - - return raii.release(); -} - -jbyteArray constructFsverityFooter(JNIEnv* env, jobject /* clazz */, - jint offsetToDescriptorHead) { - auto raii = JavaByteArrayHolder::newArray(env, sizeof(fsverity_footer)); - fsverity_footer* footer = reinterpret_cast<fsverity_footer*>(raii.getRaw()); - - footer->desc_reverse_offset = offsetToDescriptorHead + sizeof(fsverity_footer); - memcpy(footer->magic, FS_VERITY_MAGIC, sizeof(footer->magic)); - - return raii.release(); -} - const JNINativeMethod sMethods[] = { - { "enableFsverityNative", "(Ljava/lang/String;)I", (void *)enableFsverity }, + { "enableFsverityNative", "(Ljava/lang/String;[B)I", (void *)enableFsverity }, { "measureFsverityNative", "(Ljava/lang/String;)I", (void *)measureFsverity }, - { "constructFsveritySignedDataNative", "([B)[B", (void *)constructFsveritySignedData }, - { "constructFsverityDescriptorNative", "(J)[B", (void *)constructFsverityDescriptor }, - { "constructFsverityExtensionNative", "(SI)[B", (void *)constructFsverityExtension }, - { "constructFsverityFooterNative", "(I)[B", (void *)constructFsverityFooter }, }; } // namespace diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 3154c7021255..704c80870fe5 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + 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; } + writeAttributeValuesToXml(out, outerTag, TAG_PACKAGE_LIST_ITEM, packageList); + } - out.startTag(null, outerTag); - writeAttributeValuesToXml(out, TAG_PACKAGE_LIST_ITEM, packageList); - out.endTag(null, outerTag); + 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 writeAttributeValuesToXml(XmlSerializer out, String 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) @@ -5663,7 +5645,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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) { 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 2585a2832094..b7079124fb79 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 @@ public class AccessibilityGestureDetectorTest { // 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 6a0d7f192adb..9e3b54d1ca96 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.MemoryStat; 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 @@ public class MemoryStatUtilTest { } @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 d6cb9826d514..d90091017116 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 @@ public class DevicePolicyManagerTest extends DpmTestBase { } /** - * 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/stats/ProcfsMemoryUtilTest.java b/services/tests/servicestests/src/com/android/server/stats/ProcfsMemoryUtilTest.java new file mode 100644 index 000000000000..4fb533f726b2 --- /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 e15af3dbecc4..0b4760d89686 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 @@ public class NotificationComparatorTest extends UiServiceTestCase { 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 @@ public class NotificationComparatorTest extends UiServiceTestCase { 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 @@ public class NotificationComparatorTest extends UiServiceTestCase { 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 @@ public class NotificationComparatorTest extends UiServiceTestCase { 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 397d2155beeb..a9fe1a62b558 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.NotificationRankingUpdate; 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 org.junit.runner.RunWith; 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 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { 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 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { tweak.isNoisy(), (ArrayList) tweak.getSmartActions(), (ArrayList) tweak.getSmartReplies(), - tweak.canBubble() + tweak.canBubble(), + tweak.visuallyInterruptive() ); assertNotEquals(nru, nru2); } @@ -258,7 +260,8 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { getNoisy(i), getSmartActions(key, i), getSmartReplies(key, i), - canBubble(i) + canBubble(i), + visuallyInterruptive(i) ); rankings[i] = ranking; } @@ -363,6 +366,10 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { 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/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 8444ab2ebeb8..30c8eb36f34a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -658,6 +658,7 @@ public class ActivityRecordTests extends ActivityTestsBase { @Override public void onAnimationStart(RemoteAnimationTarget[] apps, + RemoteAnimationTarget[] wallpapers, IRemoteAnimationFinishedCallback finishedCallback) { } 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 8393ae0c3aec..1f672c0c95f3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -366,7 +366,7 @@ public class ActivityStarterTests extends ActivityTestsBase { // 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 4f00383d1789..d311dfc60675 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java @@ -38,7 +38,6 @@ import android.content.Intent; 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; @@ -446,12 +445,7 @@ class ActivityTestsBase { final ActivityStackSupervisor supervisor = mRootActivityContainer.mStackSupervisor; if (mWindowingMode == WINDOWING_MODE_PINNED) { stack = new ActivityStack(mDisplay, stackId, supervisor, - mWindowingMode, ACTIVITY_TYPE_STANDARD, mOnTop) { - @Override - Rect getDefaultPictureInPictureBounds(float aspectRatio) { - return new Rect(50, 50, 100, 100); - } - }; + mWindowingMode, ACTIVITY_TYPE_STANDARD, mOnTop); } else { stack = new ActivityStack(mDisplay, stackId, supervisor, mWindowingMode, mActivityType, mOnTop); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java index 035568f489be..629a95453054 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java @@ -75,6 +75,7 @@ public class AppChangeTransitionTests extends WindowTestsBase { class TestRemoteAnimationRunner implements IRemoteAnimationRunner { @Override public void onAnimationStart(RemoteAnimationTarget[] apps, + RemoteAnimationTarget[] wallpapers, IRemoteAnimationFinishedCallback finishedCallback) { for (RemoteAnimationTarget target : apps) { assertNotNull(target.startBounds); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java index c162b6a5a289..45e68902e123 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java @@ -248,6 +248,7 @@ public class AppTransitionTests extends WindowTestsBase { boolean mCancelled = false; @Override public void onAnimationStart(RemoteAnimationTarget[] apps, + RemoteAnimationTarget[] wallpapers, IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException { } 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 efd468f1f77a..e9c226340164 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 @@ public class PinnedStackControllerTest extends WindowTestsBase { verify(mIPinnedStackListener).onImeVisibilityChanged(false, 0); verify(mIPinnedStackListener).onShelfVisibilityChanged(false, 0); - verify(mIPinnedStackListener).onMovementBoundsChanged(any(), any(), any(), eq(false), - eq(false), anyInt()); + verify(mIPinnedStackListener).onMovementBoundsChanged(any(), eq(false), + eq(false)); verify(mIPinnedStackListener).onActionsChanged(any()); verify(mIPinnedStackListener).onMinimizedStateChanged(anyBoolean()); @@ -75,8 +75,8 @@ public class PinnedStackControllerTest extends WindowTestsBase { mWm.setShelfHeight(true, SHELF_HEIGHT); verify(mIPinnedStackListener).onShelfVisibilityChanged(true, SHELF_HEIGHT); - verify(mIPinnedStackListener).onMovementBoundsChanged(any(), any(), any(), eq(false), - eq(true), anyInt()); + verify(mIPinnedStackListener).onMovementBoundsChanged(any(), eq(false), + eq(true)); verify(mIPinnedStackListener, never()).onImeVisibilityChanged(anyBoolean(), anyInt()); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java index 9ca0180e507d..f792b0db6cb5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -26,13 +26,17 @@ import static android.view.WindowManager.TRANSIT_ACTIVITY_CLOSE; import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeast; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static com.android.server.wm.ActivityStackSupervisor.ON_TOP; import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE; import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION; +import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_TOP; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -44,9 +48,12 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import android.app.ActivityManager.TaskSnapshot; import android.os.Binder; +import android.os.IBinder; import android.os.IInterface; import android.platform.test.annotations.Presubmit; import android.util.SparseBooleanArray; @@ -88,8 +95,8 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { doReturn(mDisplayContent).when(mWm.mRoot).getDisplayContent(anyInt()); } when(mMockRunner.asBinder()).thenReturn(new Binder()); - mController = new RecentsAnimationController(mWm, mMockRunner, mAnimationCallbacks, - DEFAULT_DISPLAY); + mController = spy(new RecentsAnimationController(mWm, mMockRunner, mAnimationCallbacks, + DEFAULT_DISPLAY)); } @Test @@ -133,11 +140,11 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { @Test public void testIncludedApps_expectTargetAndVisible() { mWm.setRecentsAnimationController(mController); - final ActivityStack homStack = mDisplayContent.mAcitvityDisplay.getOrCreateStack( + final ActivityStack homeStack = mDisplayContent.mAcitvityDisplay.getOrCreateStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP); final AppWindowToken homeAppWindow = new ActivityTestsBase.ActivityBuilder(mWm.mAtmService) - .setStack(homStack) + .setStack(homeStack) .setCreateTask(true) .build() .mAppWindowToken; @@ -157,6 +164,102 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { } @Test + public void testWallpaperIncluded_expectTarget() throws Exception { + mWm.setRecentsAnimationController(mController); + final ActivityStack homeStack = mDisplayContent.mAcitvityDisplay.getOrCreateStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP); + final AppWindowToken homeAppWindow = + new ActivityTestsBase.ActivityBuilder(mWm.mAtmService) + .setStack(homeStack) + .setCreateTask(true) + .build() + .mAppWindowToken; + final AppWindowToken appWindow = createAppWindowToken(mDisplayContent, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); + final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, appWindow, "win1"); + appWindow.addWindow(win1); + final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm, + mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */); + spyOn(mDisplayContent.mWallpaperController); + doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible(); + + mDisplayContent.getConfiguration().windowConfiguration.setRotation( + mDisplayContent.getRotation()); + mController.initialize(ACTIVITY_TYPE_HOME, new SparseBooleanArray()); + mController.startAnimation(); + + // Ensure that we are animating the app and wallpaper target + assertTrue(mController.isAnimatingTask(appWindow.getTask())); + assertTrue(mController.isAnimatingWallpaper(wallpaperWindowToken)); + } + + @Test + public void testWallpaperAnimatorCanceled_expectAnimationKeepsRunning() throws Exception { + mWm.setRecentsAnimationController(mController); + final ActivityStack homeStack = mDisplayContent.mAcitvityDisplay.getOrCreateStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP); + final AppWindowToken homeAppWindow = + new ActivityTestsBase.ActivityBuilder(mWm.mAtmService) + .setStack(homeStack) + .setCreateTask(true) + .build() + .mAppWindowToken; + final AppWindowToken appWindow = createAppWindowToken(mDisplayContent, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); + final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, appWindow, "win1"); + appWindow.addWindow(win1); + final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm, + mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */); + spyOn(mDisplayContent.mWallpaperController); + doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible(); + + mDisplayContent.getConfiguration().windowConfiguration.setRotation( + mDisplayContent.getRotation()); + mController.initialize(ACTIVITY_TYPE_HOME, new SparseBooleanArray()); + mController.startAnimation(); + + // Cancel the animation and ensure the controller is still running + wallpaperWindowToken.cancelAnimation(); + assertTrue(mController.isAnimatingTask(appWindow.getTask())); + assertFalse(mController.isAnimatingWallpaper(wallpaperWindowToken)); + verify(mMockRunner, never()).onAnimationCanceled(null /* taskSnapshot */); + } + + @Test + public void testFinish_expectTargetAndWallpaperAdaptersRemoved() { + mWm.setRecentsAnimationController(mController); + final ActivityStack homeStack = mDisplayContent.mAcitvityDisplay.getOrCreateStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP); + final AppWindowToken homeAppWindow = + new ActivityTestsBase.ActivityBuilder(mWm.mAtmService) + .setStack(homeStack) + .setCreateTask(true) + .build() + .mAppWindowToken; + final WindowState hwin1 = createWindow(null, TYPE_BASE_APPLICATION, homeAppWindow, "hwin1"); + homeAppWindow.addWindow(hwin1); + final AppWindowToken appWindow = createAppWindowToken(mDisplayContent, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); + final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, appWindow, "win1"); + appWindow.addWindow(win1); + final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm, + mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */); + spyOn(mDisplayContent.mWallpaperController); + doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible(); + + // Start and finish the animation + mController.initialize(ACTIVITY_TYPE_HOME, new SparseBooleanArray()); + mController.startAnimation(); + // Reset at this point since we may remove adapters that couldn't be created + reset(mController); + mController.cleanupAnimation(REORDER_MOVE_TO_TOP); + + // Ensure that we remove the task (home & app) and wallpaper adapters + verify(mController, times(2)).removeAnimation(any()); + verify(mController, times(1)).removeWallpaperAnimation(any()); + } + + @Test public void testDeferCancelAnimation() throws Exception { mWm.setRecentsAnimationController(mController); final AppWindowToken appWindow = createAppWindowToken(mDisplayContent, diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java index 3e05dcc0f297..3b9c3bbdc4ba 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java @@ -19,7 +19,9 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeast; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; @@ -27,10 +29,12 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; import android.graphics.Point; import android.graphics.Rect; import android.os.Binder; +import android.os.IBinder; import android.os.IInterface; import android.platform.test.annotations.Presubmit; import android.view.IRemoteAnimationFinishedCallback; @@ -96,9 +100,12 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); + final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor = + ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor = ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class); - verify(mMockRunner).onAnimationStart(appsCaptor.capture(), finishedCaptor.capture()); + verify(mMockRunner).onAnimationStart(appsCaptor.capture(), wallpapersCaptor.capture(), + finishedCaptor.capture()); assertEquals(1, appsCaptor.getValue().length); final RemoteAnimationTarget app = appsCaptor.getValue()[0]; assertEquals(new Point(50, 100), app.position); @@ -201,9 +208,12 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); + final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor = + ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor = ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class); - verify(mMockRunner).onAnimationStart(appsCaptor.capture(), finishedCaptor.capture()); + verify(mMockRunner).onAnimationStart(appsCaptor.capture(), wallpapersCaptor.capture(), + finishedCaptor.capture()); assertEquals(1, appsCaptor.getValue().length); assertEquals(mMockLeash, appsCaptor.getValue()[0].leash); } @@ -237,9 +247,12 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); + final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor = + ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor = ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class); - verify(mMockRunner).onAnimationStart(appsCaptor.capture(), finishedCaptor.capture()); + verify(mMockRunner).onAnimationStart(appsCaptor.capture(), wallpapersCaptor.capture(), + finishedCaptor.capture()); assertEquals(1, appsCaptor.getValue().length); final RemoteAnimationTarget app = appsCaptor.getValue()[0]; assertEquals(RemoteAnimationTarget.MODE_CHANGING, app.mode); @@ -264,6 +277,66 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { } } + @Test + public void testWallpaperIncluded_expectTarget() throws Exception { + final WindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm, mock(IBinder.class), + true, mDisplayContent, true /* ownerCanManageAppTokens */); + spyOn(mDisplayContent.mWallpaperController); + doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible(); + final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); + mDisplayContent.mOpeningApps.add(win.mAppToken); + try { + final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mAppToken, + new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter; + adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); + mController.goodToGo(); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = + ArgumentCaptor.forClass(RemoteAnimationTarget[].class); + final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor = + ArgumentCaptor.forClass(RemoteAnimationTarget[].class); + final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor = + ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class); + verify(mMockRunner).onAnimationStart(appsCaptor.capture(), wallpapersCaptor.capture(), + finishedCaptor.capture()); + assertEquals(1, wallpapersCaptor.getValue().length); + } finally { + mDisplayContent.mOpeningApps.clear(); + } + } + + @Test + public void testWallpaperAnimatorCanceled_expectAnimationKeepsRunning() throws Exception { + final WindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm, mock(IBinder.class), + true, mDisplayContent, true /* ownerCanManageAppTokens */); + spyOn(mDisplayContent.mWallpaperController); + doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible(); + final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); + mDisplayContent.mOpeningApps.add(win.mAppToken); + try { + final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mAppToken, + new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter; + adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); + mController.goodToGo(); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = + ArgumentCaptor.forClass(RemoteAnimationTarget[].class); + final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor = + ArgumentCaptor.forClass(RemoteAnimationTarget[].class); + final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor = + ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class); + verify(mMockRunner).onAnimationStart(appsCaptor.capture(), wallpapersCaptor.capture(), + finishedCaptor.capture()); + assertEquals(1, wallpapersCaptor.getValue().length); + + // Cancel the wallpaper window animator and ensure the runner is not canceled + wallpaperWindowToken.cancelAnimation(); + verify(mMockRunner, never()).onAnimationCancelled(); + } finally { + mDisplayContent.mOpeningApps.clear(); + } + } + private static void verifyNoMoreInteractionsExceptAsBinder(IInterface binder) { verify(binder, atLeast(0)).asBinder(); verifyNoMoreInteractions(binder); diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java index 340e7411d21e..2b1c4fff5861 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java @@ -138,6 +138,19 @@ public class SurfaceAnimatorTest extends WindowTestsBase { } @Test + public void testCancelWithNullFinishCallbackAnimation() { + SurfaceAnimator animator = new SurfaceAnimator(mAnimatable, null, mWm); + animator.startAnimation(mTransaction, mSpec, true /* hidden */); + assertTrue(animator.isAnimating()); + assertNotNull(animator.getAnimation()); + animator.cancelAnimation(); + assertFalse(animator.isAnimating()); + assertNull(animator.getAnimation()); + verify(mSpec).onAnimationCancelled(any()); + verify(mTransaction).remove(eq(mAnimatable.mLeash)); + } + + @Test public void testDelayingAnimationStart() { mAnimatable.mSurfaceAnimator.startDelayingAnimationStart(); mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); 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 2fe2c41e0036..5a4d3991bedd 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 @@ public class SystemServicesTestRule implements TestRule { 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/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java index 99337565e128..735b9a1dcf2e 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java @@ -325,9 +325,8 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { false /* Don't notify for synchronous calls */); // Initialize power save, call active state monitoring logic. - if (status == STATUS_OK && !mRecognitionRequested) { + if (status == STATUS_OK) { initializeTelephonyAndPowerStateListeners(); - mRecognitionRequested = true; } return status; @@ -481,6 +480,9 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { if (unloadModel && modelData.isModelLoaded()) { Slog.d(TAG, "Unloading previously loaded stale model."); + if (mModule == null) { + return STATUS_ERROR; + } status = mModule.unloadSoundModel(modelData.getHandle()); MetricsLogger.count(mContext, "sth_unloading_stale_model", 1); if (status != SoundTrigger.STATUS_OK) { @@ -550,6 +552,9 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { } } + if (mModule == null) { + return STATUS_ERROR; + } int status = mModule.unloadSoundModel(modelData.getHandle()); if (status != SoundTrigger.STATUS_OK) { Slog.w(TAG, "unloadGenericSoundModel() call failed with " + status); @@ -878,6 +883,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { mContext.unregisterReceiver(mPowerSaveModeListener); mPowerSaveModeListener = null; } + mRecognitionRequested = false; } // Clears state for all models (generic and keyphrase). @@ -924,6 +930,9 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { } private void initializeTelephonyAndPowerStateListeners() { + if (mRecognitionRequested) { + return; + } long token = Binder.clearCallingIdentity(); try { // Get the current call state synchronously for the first recognition. @@ -941,6 +950,8 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { } mIsPowerSaveMode = mPowerManager.getPowerSaveState(ServiceType.SOUND) .batterySaverEnabled; + + mRecognitionRequested = true; } finally { Binder.restoreCallingIdentity(token); } @@ -987,6 +998,9 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { if (exception != null) { Slog.e(TAG, "forceStopAndUnloadModel", exception); } + if (mModule == null) { + return; + } if (modelData.isModelStarted()) { Slog.d(TAG, "Stopping previously started dangling model " + modelData.getHandle()); if (mModule.stopRecognition(modelData.getHandle()) != STATUS_OK) { @@ -1093,6 +1107,13 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { // a recognition include: no active phone call or not being in a power save mode. Also, // the native service should be enabled. private boolean isRecognitionAllowed() { + // if mRecognitionRequested is false, call and power state listeners are not registered so + // we read current state directly from services + if (!mRecognitionRequested) { + mCallActive = mTelephonyManager.getCallState() == TelephonyManager.CALL_STATE_OFFHOOK; + mIsPowerSaveMode = + mPowerManager.getPowerSaveState(ServiceType.SOUND).batterySaverEnabled; + } return !mCallActive && !mServiceDisabled && !mIsPowerSaveMode; } @@ -1116,6 +1137,9 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { return STATUS_OK; } + if (mModule == null) { + return STATUS_ERROR; + } int status = mModule.startRecognition(handle, config); if (status != SoundTrigger.STATUS_OK) { Slog.w(TAG, "startRecognition failed with " + status); @@ -1152,8 +1176,11 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { } private int stopRecognitionLocked(ModelData modelData, boolean notify) { - IRecognitionStatusCallback callback = modelData.getCallback(); + if (mModule == null) { + return STATUS_ERROR; + } + IRecognitionStatusCallback callback = modelData.getCallback(); // Stop recognition. int status = STATUS_OK; diff --git a/services/wifi/java/android/net/wifi/WifiStackClient.java b/services/wifi/java/android/net/wifi/WifiStackClient.java index fa66e4c5eeea..64af7a8845a7 100644 --- a/services/wifi/java/android/net/wifi/WifiStackClient.java +++ b/services/wifi/java/android/net/wifi/WifiStackClient.java @@ -21,6 +21,7 @@ import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL; import android.annotation.NonNull; import android.content.Context; import android.net.ConnectivityModuleConnector; +import android.os.HandlerThread; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; @@ -56,13 +57,20 @@ public class WifiStackClient { public void onModuleServiceConnected(IBinder service) { Log.i(TAG, "Wifi stack connected"); - registerWifiStackService(service); - IWifiStackConnector connector = IWifiStackConnector.Stub.asInterface(service); - registerApiServiceAndStart(connector, Context.WIFI_SERVICE); - registerApiServiceAndStart(connector, Context.WIFI_SCANNING_SERVICE); - registerApiServiceAndStart(connector, Context.WIFI_P2P_SERVICE); - registerApiServiceAndStart(connector, Context.WIFI_AWARE_SERVICE); - registerApiServiceAndStart(connector, Context.WIFI_RTT_RANGING_SERVICE); + // spin up a new thread to not block system_server main thread + HandlerThread thread = new HandlerThread("InitWifiServicesThread"); + thread.start(); + thread.getThreadHandler().post(() -> { + registerWifiStackService(service); + IWifiStackConnector connector = IWifiStackConnector.Stub.asInterface(service); + registerApiServiceAndStart(connector, Context.WIFI_SCANNING_SERVICE); + registerApiServiceAndStart(connector, Context.WIFI_SERVICE); + registerApiServiceAndStart(connector, Context.WIFI_P2P_SERVICE); + registerApiServiceAndStart(connector, Context.WIFI_AWARE_SERVICE); + registerApiServiceAndStart(connector, Context.WIFI_RTT_RANGING_SERVICE); + + thread.quitSafely(); + }); } } diff --git a/startop/apps/test/Android.bp b/startop/apps/test/Android.bp index db1d305070d9..a8063209b56f 100644 --- a/startop/apps/test/Android.bp +++ b/startop/apps/test/Android.bp @@ -17,6 +17,7 @@ android_app { name: "startop_test_app", srcs: [ + "src/CPUIntensive.java", "src/EmptyActivity.java", "src/LayoutInflationActivity.java", "src/ComplexLayoutInflationActivity.java", diff --git a/startop/apps/test/src/CPUIntensive.java b/startop/apps/test/src/CPUIntensive.java new file mode 100644 index 000000000000..a411e8ce8ce0 --- /dev/null +++ b/startop/apps/test/src/CPUIntensive.java @@ -0,0 +1,67 @@ +/* + * 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. + */ + +/** + * A threaded CPU intensive class for use in benchmarks. + */ + +package com.android.startop.test; + +final class CPUIntensive { + public static final int THREAD_COUNT = 8; + public static final int ARRAY_SIZE = 30000; + public static int[][] array = new int[THREAD_COUNT][ARRAY_SIZE]; + + static class WorkerThread extends Thread { + int mThreadNumber; + WorkerThread(int number) { + mThreadNumber = number; + } + public void run() { + final int arrayLength = array[mThreadNumber].length; + for (int i = 0; i < arrayLength; ++i) { + array[mThreadNumber][i] = i * i; + } + for (int i = 0; i < arrayLength; ++i) { + for (int j = 0; j < arrayLength; ++j) { + int swap = array[mThreadNumber][j]; + array[mThreadNumber][j] = array[mThreadNumber][(j + i) % arrayLength]; + array[mThreadNumber][(j + i) % arrayLength] = swap; + } + } + } + }; + + public static void doSomeWork(int threadCount) { + WorkerThread[] threads = new WorkerThread[threadCount]; + // Create the threads. + for (int i = 0; i < threadCount; ++i) { + threads[i] = new WorkerThread(i); + } + // Start the threads. + for (int i = 0; i < threadCount; ++i) { + threads[i].start(); + } + // Join the threads. + for (int i = 0; i < threadCount; ++i) { + try { + threads[i].join(); + } catch (Exception ex) { + } + } + } +} + diff --git a/startop/apps/test/src/SystemServerBenchmarkActivity.java b/startop/apps/test/src/SystemServerBenchmarkActivity.java index ed3cbe9e3e30..c8d9fde0bdaf 100644 --- a/startop/apps/test/src/SystemServerBenchmarkActivity.java +++ b/startop/apps/test/src/SystemServerBenchmarkActivity.java @@ -26,15 +26,11 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.os.AsyncTask; import android.os.Bundle; -import android.os.Trace; -import android.view.LayoutInflater; -import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.GridLayout; import android.widget.TextView; -import java.util.Arrays; class Benchmark { // Time limit to run benchmarks in seconds @@ -107,6 +103,22 @@ public class SystemServerBenchmarkActivity extends Activity { new Benchmark(benchmarkList, "Empty", () -> { }); + new Benchmark(benchmarkList, "CPU Intensive (1 thread)", () -> { + CPUIntensive.doSomeWork(1); + }); + + new Benchmark(benchmarkList, "CPU Intensive (2 thread)", () -> { + CPUIntensive.doSomeWork(2); + }); + + new Benchmark(benchmarkList, "CPU Intensive (4 thread)", () -> { + CPUIntensive.doSomeWork(4); + }); + + new Benchmark(benchmarkList, "CPU Intensive (8 thread)", () -> { + CPUIntensive.doSomeWork(8); + }); + PackageManager pm = getPackageManager(); new Benchmark(benchmarkList, "getInstalledApplications", () -> { pm.getInstalledApplications(PackageManager.MATCH_SYSTEM_ONLY); diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java index 35488100fb58..0abd9fc62b14 100644 --- a/telecomm/java/android/telecom/ConnectionService.java +++ b/telecomm/java/android/telecom/ConnectionService.java @@ -30,6 +30,7 @@ import android.os.ParcelFileDescriptor; 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 @@ public abstract class ConnectionService extends Service { 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 949f7b7a89ae..49c3a7205d59 100644 --- a/telecomm/java/android/telecom/Logging/SessionManager.java +++ b/telecomm/java/android/telecom/Logging/SessionManager.java @@ -391,6 +391,20 @@ public class SessionManager { 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 58f285851375..f527bc3b6df6 100644 --- a/telephony/java/android/telephony/SubscriptionInfo.java +++ b/telephony/java/android/telephony/SubscriptionInfo.java @@ -578,7 +578,8 @@ public class SubscriptionInfo implements Parcelable { 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 d3cba2e3e889..51de903ed37e 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -2701,7 +2701,8 @@ public class SubscriptionManager { 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 f25b012389c4..ea55a82cb8fe 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -270,9 +270,6 @@ public class TelephonyManager { private SubscriptionManager mSubscriptionManager; private TelephonyScanManager mTelephonyScanManager; - private static String multiSimConfig = - SystemProperties.get(TelephonyProperties.PROPERTY_MULTI_SIM_CONFIG); - /** Enum indicating multisim variants * DSDS - Dual SIM Dual Standby * DSDA - Dual SIM Dual Active @@ -364,7 +361,6 @@ public class TelephonyManager { } } - /** * Returns the number of phones available. * Returns 0 if none of voice, sms, data is not supported @@ -397,6 +393,31 @@ public class TelephonyManager { return phoneCount; } + /** + * + * Return how many phone / logical modem can be active simultaneously, in terms of device + * capability. + * For example, for a dual-SIM capable device, it always returns 2, even if only one logical + * modem / SIM is active (aka in single SIM mode). + * + * TODO: b/139642279 publicize and rename. + * @hide + */ + public static int getMaxPhoneCount() { + // TODO: b/139642279 when turning on this feature, remove dependency of + // PROPERTY_REBOOT_REQUIRED_ON_MODEM_CHANGE and always return result based on + // PROPERTY_MAX_ACTIVE_MODEMS. + String rebootRequired = SystemProperties.get( + TelephonyProperties.PROPERTY_REBOOT_REQUIRED_ON_MODEM_CHANGE); + if (rebootRequired.equals("false")) { + // If no reboot is required, return max possible active modems. + return SystemProperties.getInt( + TelephonyProperties.PROPERTY_MAX_ACTIVE_MODEMS, getDefault().getPhoneCount()); + } else { + return getDefault().getPhoneCount(); + } + } + /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public static TelephonyManager from(Context context) { @@ -432,8 +453,7 @@ public class TelephonyManager { /** {@hide} */ @UnsupportedAppUsage public boolean isMultiSimEnabled() { - return (multiSimConfig.equals("dsds") || multiSimConfig.equals("dsda") || - multiSimConfig.equals("tsts")); + return getPhoneCount() > 1; } // @@ -3251,6 +3271,31 @@ public class TelephonyManager { } } + + /** + * 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. * @@ -6600,11 +6645,7 @@ public class TelephonyManager { public int getSimCount() { // FIXME Need to get it from Telephony Dev Controller when that gets implemented! // and then this method shouldn't be used at all! - if(isMultiSimEnabled()) { - return getPhoneCount(); - } else { - return 1; - } + return getPhoneCount(); } /** diff --git a/telephony/java/android/telephony/UiccAccessRule.java b/telephony/java/android/telephony/UiccAccessRule.java index 811722f0bbff..93ccba1dd996 100644 --- a/telephony/java/android/telephony/UiccAccessRule.java +++ b/telephony/java/android/telephony/UiccAccessRule.java @@ -20,6 +20,7 @@ import android.annotation.Nullable; 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 @@ public final class UiccAccessRule implements Parcelable { * * @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) { + Signature[] signatures = packageInfo.signatures; + SigningInfo sInfo = packageInfo.signingInfo; + + if (sInfo != null) { + signatures = sInfo.getSigningCertificateHistory(); + if (sInfo.hasMultipleSigners()) { + signatures = sInfo.getApkContentsSigners(); + } + } + + if (signatures == null || signatures.length == 0) { throw new IllegalArgumentException( - "Must use GET_SIGNATURES when looking up package info"); + "Must use GET_SIGNING_CERTIFICATES when looking up package info"); } - for (Signature sig : packageInfo.signatures) { + 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/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java index 77ee20512d11..4ddeb908a200 100644 --- a/telephony/java/android/telephony/ims/ImsCallProfile.java +++ b/telephony/java/android/telephony/ims/ImsCallProfile.java @@ -309,6 +309,37 @@ public final class ImsCallProfile implements Parcelable { public @CallRestrictCause int mRestrictCause = CALL_RESTRICT_CAUSE_NONE; /** + * The VERSTAT for an incoming call's phone number. + */ + private @VerificationStatus int mCallerNumberVerificationStatus; + + /** + * Indicates that the network could not perform verification. + */ + public static final int VERIFICATION_STATUS_NOT_VERIFIED = 0; + + /** + * Indicates that verification by the network passed. This indicates there is a high likelihood + * that the call originated from a valid source. + */ + public static final int VERIFICATION_STATUS_PASSED = 1; + + /** + * Indicates that verification by the network failed. This indicates there is a high likelihood + * that the call did not originate from a valid source. + */ + public static final int VERIFICATION_STATUS_FAILED = 2; + + /**@hide*/ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "VERIFICATION_STATUS_", value = { + VERIFICATION_STATUS_NOT_VERIFIED, + VERIFICATION_STATUS_PASSED, + VERIFICATION_STATUS_FAILED + }) + public @interface VerificationStatus {} + + /** * The emergency service categories, only valid if {@link #getServiceType} returns * {@link #SERVICE_TYPE_EMERGENCY} * @@ -539,6 +570,29 @@ public final class ImsCallProfile implements Parcelable { mMediaProfile = profile.mMediaProfile; } + /** + * Sets the verification status for the phone number of an incoming call as identified in + * ATIS-1000082. + * <p> + * The ImsService should parse the verstat information from the SIP INVITE headers for the call + * to determine this information. It is typically found in the P-Asserted-Identity OR From + * header fields. + * @param callerNumberVerificationStatus the new verification status. + */ + public void setCallerNumberVerificationStatus( + @VerificationStatus int callerNumberVerificationStatus) { + mCallerNumberVerificationStatus = callerNumberVerificationStatus; + } + + /** + * Gets the verification status for the phone number of an incoming call as identified in + * ATIS-1000082. + * @return the verification status. + */ + public @VerificationStatus int getCallerNumberVerificationStatus() { + return mCallerNumberVerificationStatus; + } + @NonNull @Override public String toString() { @@ -551,7 +605,8 @@ public final class ImsCallProfile implements Parcelable { + ", emergencyCallRouting=" + mEmergencyCallRouting + ", emergencyCallTesting=" + mEmergencyCallTesting + ", hasKnownUserIntentEmergency=" + mHasKnownUserIntentEmergency - + ", mRestrictCause=" + mRestrictCause + " }"; + + ", mRestrictCause=" + mRestrictCause + + ", mCallerNumberVerstat= " + mCallerNumberVerificationStatus + " }"; } @Override @@ -572,6 +627,7 @@ public final class ImsCallProfile implements Parcelable { out.writeBoolean(mEmergencyCallTesting); out.writeBoolean(mHasKnownUserIntentEmergency); out.writeInt(mRestrictCause); + out.writeInt(mCallerNumberVerificationStatus); } private void readFromParcel(Parcel in) { @@ -585,6 +641,7 @@ public final class ImsCallProfile implements Parcelable { mEmergencyCallTesting = in.readBoolean(); mHasKnownUserIntentEmergency = in.readBoolean(); mRestrictCause = in.readInt(); + mCallerNumberVerificationStatus = in.readInt(); } public static final @android.annotation.NonNull Creator<ImsCallProfile> CREATOR = new Creator<ImsCallProfile>() { diff --git a/telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl b/telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl index 6a35e33f44f9..5aa58c1ee7ee 100644 --- a/telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl @@ -21,7 +21,7 @@ package android.telephony.ims.aidl; * {@hide} */ oneway interface IImsSmsListener { - void onSendSmsResult(int token, int messageRef, int status, int reason); + void onSendSmsResult(int token, int messageRef, int status, int reason, int networkErrorCode); void onSmsStatusReportReceived(int token, in String format, in byte[] pdu); void onSmsReceived(int token, in String format, in byte[] pdu); } diff --git a/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java index 2e4bfb3149be..175769bd34e4 100644 --- a/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java @@ -118,6 +118,12 @@ public class ImsSmsImplBase { */ public static final int STATUS_REPORT_STATUS_ERROR = 2; + /** + * No network error was generated while processing the SMS message. + */ + // Should match SmsResponse.NO_ERROR_CODE + public static final int RESULT_NO_NETWORK_ERROR = -1; + // Lock for feature synchronization private final Object mLock = new Object(); private IImsSmsListener mListener; @@ -231,16 +237,37 @@ public class ImsSmsImplBase { } /** + * This method should be triggered by the IMS providers when an outgoing SMS message has been + * sent successfully. + * + * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])} + * @param messageRef the message reference. Should be between 0 and 255 per TS.123.040 + * + * @throws RuntimeException if called before {@link #onReady()} is triggered or if the + * connection to the framework is not available. If this happens attempting to send the SMS + * should be aborted. + */ + public final void onSendSmsResultSuccess(int token, int messageRef) throws RuntimeException { + synchronized (mLock) { + if (mListener == null) { + throw new RuntimeException("Feature not ready."); + } + try { + mListener.onSendSmsResult(token, messageRef, SEND_STATUS_OK, + SmsManager.RESULT_ERROR_NONE, RESULT_NO_NETWORK_ERROR); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + } + + /** * This method should be triggered by the IMS providers to pass the result of the sent message * to the platform. * * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])} * @param messageRef the message reference. Should be between 0 and 255 per TS.123.040 - * @param status result of sending the SMS. Valid values are: - * {@link #SEND_STATUS_OK}, - * {@link #SEND_STATUS_ERROR}, - * {@link #SEND_STATUS_ERROR_RETRY}, - * {@link #SEND_STATUS_ERROR_FALLBACK}, + * @param status result of sending the SMS. * @param reason reason in case status is failure. Valid values are: * {@link SmsManager#RESULT_ERROR_NONE}, * {@link SmsManager#RESULT_ERROR_GENERIC_FAILURE}, @@ -271,7 +298,11 @@ public class ImsSmsImplBase { * @throws RuntimeException if called before {@link #onReady()} is triggered or if the * connection to the framework is not available. If this happens attempting to send the SMS * should be aborted. + * @deprecated Use {@link #onSendSmsResultSuccess(int, int)} or + * {@link #onSendSmsResultError(int, int, int, int, int)} to notify the framework of the SMS + * send result. */ + @Deprecated public final void onSendSmsResult(int token, int messageRef, @SendStatusResult int status, int reason) throws RuntimeException { synchronized (mLock) { @@ -279,7 +310,65 @@ public class ImsSmsImplBase { throw new RuntimeException("Feature not ready."); } try { - mListener.onSendSmsResult(token, messageRef, status, reason); + mListener.onSendSmsResult(token, messageRef, status, reason, + RESULT_NO_NETWORK_ERROR); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + } + + /** + * This method should be triggered by the IMS providers when an outgoing message fails to be + * sent due to an error generated while processing the message or after being sent to the + * network. + * + * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])} + * @param messageRef the message reference. Should be between 0 and 255 per TS.123.040 + * @param status result of sending the SMS. + * @param reason Valid values are: + * {@link SmsManager#RESULT_ERROR_NONE}, + * {@link SmsManager#RESULT_ERROR_GENERIC_FAILURE}, + * {@link SmsManager#RESULT_ERROR_RADIO_OFF}, + * {@link SmsManager#RESULT_ERROR_NULL_PDU}, + * {@link SmsManager#RESULT_ERROR_NO_SERVICE}, + * {@link SmsManager#RESULT_ERROR_LIMIT_EXCEEDED}, + * {@link SmsManager#RESULT_ERROR_FDN_CHECK_FAILURE}, + * {@link SmsManager#RESULT_ERROR_SHORT_CODE_NOT_ALLOWED}, + * {@link SmsManager#RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED}, + * {@link SmsManager#RESULT_RADIO_NOT_AVAILABLE}, + * {@link SmsManager#RESULT_NETWORK_REJECT}, + * {@link SmsManager#RESULT_INVALID_ARGUMENTS}, + * {@link SmsManager#RESULT_INVALID_STATE}, + * {@link SmsManager#RESULT_NO_MEMORY}, + * {@link SmsManager#RESULT_INVALID_SMS_FORMAT}, + * {@link SmsManager#RESULT_SYSTEM_ERROR}, + * {@link SmsManager#RESULT_MODEM_ERROR}, + * {@link SmsManager#RESULT_NETWORK_ERROR}, + * {@link SmsManager#RESULT_ENCODING_ERROR}, + * {@link SmsManager#RESULT_INVALID_SMSC_ADDRESS}, + * {@link SmsManager#RESULT_OPERATION_NOT_ALLOWED}, + * {@link SmsManager#RESULT_INTERNAL_ERROR}, + * {@link SmsManager#RESULT_NO_RESOURCES}, + * {@link SmsManager#RESULT_CANCELLED}, + * {@link SmsManager#RESULT_REQUEST_NOT_SUPPORTED} + * @param networkErrorCode the error code reported by the carrier network if sending this SMS + * has resulted in an error or {@link #RESULT_NO_NETWORK_ERROR} if no network error was + * generated. See 3GPP TS 24.011 Section 7.3.4 for valid error codes and more information. + * + * @throws RuntimeException if called before {@link #onReady()} is triggered or if the + * connection to the framework is not available. If this happens attempting to send the SMS + * should be aborted. + */ + public final void onSendSmsResultError(int token, int messageRef, @SendStatusResult int status, + int reason, int networkErrorCode) + throws RuntimeException { + synchronized (mLock) { + if (mListener == null) { + throw new RuntimeException("Feature not ready."); + } + try { + mListener.onSendSmsResult(token, messageRef, status, reason, networkErrorCode); } catch (RemoteException e) { e.rethrowFromSystemServer(); } diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 9f1a2f765eee..866e936a1211 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -2012,6 +2012,13 @@ interface ITelephony { */ 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/android/internal/telephony/TelephonyProperties.java b/telephony/java/com/android/internal/telephony/TelephonyProperties.java index ef4850716701..4e42c20d28db 100644 --- a/telephony/java/com/android/internal/telephony/TelephonyProperties.java +++ b/telephony/java/com/android/internal/telephony/TelephonyProperties.java @@ -234,4 +234,11 @@ public interface TelephonyProperties String DISPLAY_OPPORTUNISTIC_SUBSCRIPTION_CARRIER_TEXT_PROPERTY_NAME = "persist.radio.display_opportunistic_carrier"; + /** + * How many logical modems can be active simultaneously. For example, if a device is dual-SIM + * capable but currently only one SIM slot and one logical modem is active, this value is still + * two. + * Type: int + */ + static final String PROPERTY_MAX_ACTIVE_MODEMS = "ro.telephony.max.active.modems"; } 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 000000000000..12e4b7e26e1e --- /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 000000000000..2836c3075b3b --- /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 000000000000..5be33ed1fac9 --- /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 000000000000..c9f96a66ab3b --- /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 000000000000..ae447d7a7417 --- /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 000000000000..483fa7f9842e --- /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 000000000000..27da35e2d928 --- /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 000000000000..7093ac63338c --- /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 000000000000..41662750842f --- /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 000000000000..ebf16ac7e632 --- /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 000000000000..e108f7600baf --- /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 000000000000..b561bd4ab3a7 --- /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 000000000000..3c70f86a0890 --- /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 000000000000..51914e4110b0 --- /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 000000000000..e24bf21a11b5 --- /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 000000000000..8551b2f9b693 --- /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 000000000000..b5244645fda1 --- /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 000000000000..f48399410723 --- /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 000000000000..09b775118dc3 --- /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 000000000000..93f30657bf1b --- /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 000000000000..9d6535c72e90 --- /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 000000000000..e38c62dde622 --- /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 000000000000..9696bc259d00 --- /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 000000000000..03755af4189c --- /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 000000000000..b85982791ada --- /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 000000000000..c1b7f934c0f7 --- /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 000000000000..c9f96a66ab3b --- /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 000000000000..ab5d48a4ce3d --- /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 000000000000..118de465a518 --- /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 000000000000..0e8ec91f4ef6 --- /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 000000000000..94e38946f632 --- /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 000000000000..1ecd1bf93e7f --- /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 000000000000..2dd1dc11c2a9 --- /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 000000000000..c9f96a66ab3b --- /dev/null +++ b/telephony/java/com/google/android/mms/util/package.html @@ -0,0 +1,5 @@ +<body> + +{@hide} + +</body> diff --git a/test-mock/Android.bp b/test-mock/Android.bp index 34ac3dcc824f..adc9e2251c0b 100644 --- a/test-mock/Android.bp +++ b/test-mock/Android.bp @@ -21,7 +21,7 @@ java_sdk_library { srcs: [ "src/**/*.java", - ":framework-srcs", + ":framework-all-sources", ], api_packages: [ diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp index 7ffa5ffc09fe..137fbd671865 100644 --- a/tools/aapt2/Debug.cpp +++ b/tools/aapt2/Debug.cpp @@ -246,6 +246,36 @@ class ValueBodyPrinter : public ConstValueVisitor { 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 @@ void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions& break; } + if (entry->overlayable_item) { + printer->Print(" OVERLAYABLE"); + } + printer->Println(); if (options.show_values) { @@ -525,4 +559,62 @@ void Debug::DumpXml(const xml::XmlResource& doc, Printer* printer) { 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 a43197cacf7b..9443d606d7e5 100644 --- a/tools/aapt2/Debug.h +++ b/tools/aapt2/Debug.h @@ -39,6 +39,7 @@ struct Debug { 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/Main.cpp b/tools/aapt2/Main.cpp index 213bdd2372ec..7966ba27ebd8 100644 --- a/tools/aapt2/Main.cpp +++ b/tools/aapt2/Main.cpp @@ -177,6 +177,11 @@ int MainImpl(int argc, char** argv) { return main_command->Execute(args, &std::cerr); } +// TODO(b/141312058) stop leaks +extern "C" const char *__asan_default_options() { + return "detect_leaks=0"; +} + int main(int argc, char** argv) { #ifdef _WIN32 LPWSTR* wide_argv = CommandLineToArgvW(GetCommandLineW(), &argc); diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp index 429aff1ff594..3982d12f6036 100644 --- a/tools/aapt2/cmd/Dump.cpp +++ b/tools/aapt2/cmd/Dump.cpp @@ -394,6 +394,17 @@ int DumpXmlTreeCommand::Dump(LoadedApk* apk) { 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 7ded9bcf8470..cd51f7a7718c 100644 --- a/tools/aapt2/cmd/Dump.h +++ b/tools/aapt2/cmd/Dump.h @@ -240,6 +240,16 @@ class DumpXmlTreeCommand : public DumpApkCommand { 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 @@ class DumpCommand : public Command { 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/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java index 66dc992d6dc7..075531ce158e 100644 --- a/wifi/java/android/net/wifi/WifiScanner.java +++ b/wifi/java/android/net/wifi/WifiScanner.java @@ -750,7 +750,10 @@ public class WifiScanner { /** * Enable/Disable wifi scanning. - * + * Note: WifiService calls this after any client interface mode changes (i.e. a new interface + * set up or an existing interface torn down) + * If there are >= 1 active client interface, invoke setScanningEnabled(true) + * If there are 0 active client interface, invoke setScanningEnabled(false) * {@hide} */ @RequiresPermission(Manifest.permission.NETWORK_STACK) |