lineage_patches_unified/patches/platform_frameworks_base/0002-backlight-Fix-backlight-control-on-Galaxy-S9.patch
2020-10-29 03:02:26 +00:00

8113 lines
326 KiB
Diff

From ed832370c42587b4d880ff3991492b67e1d97452 Mon Sep 17 00:00:00 2001
From: Pierre-Hugues Husson <phh@phh.me>
Date: Sat, 24 Mar 2018 08:01:48 +0100
Subject: [PATCH] backlight: Fix backlight control on Galaxy S9(+)
Change-Id: I1fbbb47939c377597ef8ad6b88b2acea5f4acaa6
:backlight: S9 brightness override only for screen
Change-Id: Ie16a46336fa64850014b962429f7a20ff569222f
:backlight: [WIP] Fix OP6 brightness
Change-Id: If08959ece6cac1f27e1f1a0bd966ee8e1813241d
:backlight: Try to make brightness more generic using property set by rw-system
Change-Id: I0f20ca4b1f0fa1fcfd19833aa291fbdf16d6eedd
:backlight: Add Qualcomm starlte
Change-Id: I12a445344deb8b2e59a2f6ce6b24c1ffe5675092
:backlight: Switch samsung light fingerprint match to regexp, to include Note9
Change-Id: I2995f7bab615aec125927a5a027ad8f9ae43405f
Add a property toggle to enable high brightness range on samsung device
Change-Id: I649a3985ef87f46a5515a63935fdae9cdcbd8ec5
:backlight: Add japanese S9
Change-Id: I5e245469f5f51fed14c6080e5be72506e10389e0
:backlight: Fix backlight on S10*. Add an additional property to check, so testers can try it more easily
Change-Id: Ia224e641cad8561201b4dee3d896362bee80c903
:backlight: Make samsung light HAL more overridable
Change-Id: Ie04f394f8a614da8070f330bcadbcbe12895bed0
Use new backlight control API only for backlight, not for other lights
Change-Id: I35c35fabff8b275f35671dcb8578b96dcad526f1
:backlight: fixup
Change-Id: I4e85178327d2bb63d5d0a37786058843662a89ba
---
core/java/android/view/KeyEvent.java.orig | 3170 +++++++++++
.../server/StorageManagerService.java.orig | 4790 +++++++++++++++++
.../android/server/lights/LightsService.java | 49 +-
3 files changed, 8008 insertions(+), 1 deletion(-)
create mode 100644 core/java/android/view/KeyEvent.java.orig
create mode 100644 services/core/java/com/android/server/StorageManagerService.java.orig
diff --git a/core/java/android/view/KeyEvent.java.orig b/core/java/android/view/KeyEvent.java.orig
new file mode 100644
index 00000000000..144dd8b3c36
--- /dev/null
+++ b/core/java/android/view/KeyEvent.java.orig
@@ -0,0 +1,3170 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.view.Display.INVALID_DISPLAY;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.method.MetaKeyKeyListener;
+import android.util.Log;
+import android.util.SparseIntArray;
+import android.view.KeyCharacterMap.KeyData;
+
+/**
+ * Object used to report key and button events.
+ * <p>
+ * Each key press is described by a sequence of key events. A key press
+ * starts with a key event with {@link #ACTION_DOWN}. If the key is held
+ * sufficiently long that it repeats, then the initial down is followed
+ * additional key events with {@link #ACTION_DOWN} and a non-zero value for
+ * {@link #getRepeatCount()}. The last key event is a {@link #ACTION_UP}
+ * for the key up. If the key press is canceled, the key up event will have the
+ * {@link #FLAG_CANCELED} flag set.
+ * </p><p>
+ * Key events are generally accompanied by a key code ({@link #getKeyCode()}),
+ * scan code ({@link #getScanCode()}) and meta state ({@link #getMetaState()}).
+ * Key code constants are defined in this class. Scan code constants are raw
+ * device-specific codes obtained from the OS and so are not generally meaningful
+ * to applications unless interpreted using the {@link KeyCharacterMap}.
+ * Meta states describe the pressed state of key modifiers
+ * such as {@link #META_SHIFT_ON} or {@link #META_ALT_ON}.
+ * </p><p>
+ * Key codes typically correspond one-to-one with individual keys on an input device.
+ * Many keys and key combinations serve quite different functions on different
+ * input devices so care must be taken when interpreting them. Always use the
+ * {@link KeyCharacterMap} associated with the input device when mapping keys
+ * to characters. Be aware that there may be multiple key input devices active
+ * at the same time and each will have its own key character map.
+ * </p><p>
+ * As soft input methods can use multiple and inventive ways of inputting text,
+ * there is no guarantee that any key press on a soft keyboard will generate a key
+ * event: this is left to the IME's discretion, and in fact sending such events is
+ * discouraged. You should never rely on receiving KeyEvents for any key on a soft
+ * input method. In particular, the default software keyboard will never send any
+ * key event to any application targetting Jelly Bean or later, and will only send
+ * events for some presses of the delete and return keys to applications targetting
+ * Ice Cream Sandwich or earlier. Be aware that other software input methods may
+ * never send key events regardless of the version. Consider using editor actions
+ * like {@link android.view.inputmethod.EditorInfo#IME_ACTION_DONE} if you need
+ * specific interaction with the software keyboard, as it gives more visibility to
+ * the user as to how your application will react to key presses.
+ * </p><p>
+ * When interacting with an IME, the framework may deliver key events
+ * with the special action {@link #ACTION_MULTIPLE} that either specifies
+ * that single repeated key code or a sequence of characters to insert.
+ * </p><p>
+ * In general, the framework cannot guarantee that the key events it delivers
+ * to a view always constitute complete key sequences since some events may be dropped
+ * or modified by containing views before they are delivered. The view implementation
+ * should be prepared to handle {@link #FLAG_CANCELED} and should tolerate anomalous
+ * situations such as receiving a new {@link #ACTION_DOWN} without first having
+ * received an {@link #ACTION_UP} for the prior key press.
+ * </p><p>
+ * Refer to {@link InputDevice} for more information about how different kinds of
+ * input devices and sources represent keys and buttons.
+ * </p>
+ */
+public class KeyEvent extends InputEvent implements Parcelable {
+ /** Key code constant: Unknown key code. */
+ public static final int KEYCODE_UNKNOWN = 0;
+ /** Key code constant: Soft Left key.
+ * Usually situated below the display on phones and used as a multi-function
+ * feature key for selecting a software defined function shown on the bottom left
+ * of the display. */
+ public static final int KEYCODE_SOFT_LEFT = 1;
+ /** Key code constant: Soft Right key.
+ * Usually situated below the display on phones and used as a multi-function
+ * feature key for selecting a software defined function shown on the bottom right
+ * of the display. */
+ public static final int KEYCODE_SOFT_RIGHT = 2;
+ /** Key code constant: Home key.
+ * This key is handled by the framework and is never delivered to applications. */
+ public static final int KEYCODE_HOME = 3;
+ /** Key code constant: Back key. */
+ public static final int KEYCODE_BACK = 4;
+ /** Key code constant: Call key. */
+ public static final int KEYCODE_CALL = 5;
+ /** Key code constant: End Call key. */
+ public static final int KEYCODE_ENDCALL = 6;
+ /** Key code constant: '0' key. */
+ public static final int KEYCODE_0 = 7;
+ /** Key code constant: '1' key. */
+ public static final int KEYCODE_1 = 8;
+ /** Key code constant: '2' key. */
+ public static final int KEYCODE_2 = 9;
+ /** Key code constant: '3' key. */
+ public static final int KEYCODE_3 = 10;
+ /** Key code constant: '4' key. */
+ public static final int KEYCODE_4 = 11;
+ /** Key code constant: '5' key. */
+ public static final int KEYCODE_5 = 12;
+ /** Key code constant: '6' key. */
+ public static final int KEYCODE_6 = 13;
+ /** Key code constant: '7' key. */
+ public static final int KEYCODE_7 = 14;
+ /** Key code constant: '8' key. */
+ public static final int KEYCODE_8 = 15;
+ /** Key code constant: '9' key. */
+ public static final int KEYCODE_9 = 16;
+ /** Key code constant: '*' key. */
+ public static final int KEYCODE_STAR = 17;
+ /** Key code constant: '#' key. */
+ public static final int KEYCODE_POUND = 18;
+ /** Key code constant: Directional Pad Up key.
+ * May also be synthesized from trackball motions. */
+ public static final int KEYCODE_DPAD_UP = 19;
+ /** Key code constant: Directional Pad Down key.
+ * May also be synthesized from trackball motions. */
+ public static final int KEYCODE_DPAD_DOWN = 20;
+ /** Key code constant: Directional Pad Left key.
+ * May also be synthesized from trackball motions. */
+ public static final int KEYCODE_DPAD_LEFT = 21;
+ /** Key code constant: Directional Pad Right key.
+ * May also be synthesized from trackball motions. */
+ public static final int KEYCODE_DPAD_RIGHT = 22;
+ /** Key code constant: Directional Pad Center key.
+ * May also be synthesized from trackball motions. */
+ public static final int KEYCODE_DPAD_CENTER = 23;
+ /** Key code constant: Volume Up key.
+ * Adjusts the speaker volume up. */
+ public static final int KEYCODE_VOLUME_UP = 24;
+ /** Key code constant: Volume Down key.
+ * Adjusts the speaker volume down. */
+ public static final int KEYCODE_VOLUME_DOWN = 25;
+ /** Key code constant: Power key. */
+ public static final int KEYCODE_POWER = 26;
+ /** Key code constant: Camera key.
+ * Used to launch a camera application or take pictures. */
+ public static final int KEYCODE_CAMERA = 27;
+ /** Key code constant: Clear key. */
+ public static final int KEYCODE_CLEAR = 28;
+ /** Key code constant: 'A' key. */
+ public static final int KEYCODE_A = 29;
+ /** Key code constant: 'B' key. */
+ public static final int KEYCODE_B = 30;
+ /** Key code constant: 'C' key. */
+ public static final int KEYCODE_C = 31;
+ /** Key code constant: 'D' key. */
+ public static final int KEYCODE_D = 32;
+ /** Key code constant: 'E' key. */
+ public static final int KEYCODE_E = 33;
+ /** Key code constant: 'F' key. */
+ public static final int KEYCODE_F = 34;
+ /** Key code constant: 'G' key. */
+ public static final int KEYCODE_G = 35;
+ /** Key code constant: 'H' key. */
+ public static final int KEYCODE_H = 36;
+ /** Key code constant: 'I' key. */
+ public static final int KEYCODE_I = 37;
+ /** Key code constant: 'J' key. */
+ public static final int KEYCODE_J = 38;
+ /** Key code constant: 'K' key. */
+ public static final int KEYCODE_K = 39;
+ /** Key code constant: 'L' key. */
+ public static final int KEYCODE_L = 40;
+ /** Key code constant: 'M' key. */
+ public static final int KEYCODE_M = 41;
+ /** Key code constant: 'N' key. */
+ public static final int KEYCODE_N = 42;
+ /** Key code constant: 'O' key. */
+ public static final int KEYCODE_O = 43;
+ /** Key code constant: 'P' key. */
+ public static final int KEYCODE_P = 44;
+ /** Key code constant: 'Q' key. */
+ public static final int KEYCODE_Q = 45;
+ /** Key code constant: 'R' key. */
+ public static final int KEYCODE_R = 46;
+ /** Key code constant: 'S' key. */
+ public static final int KEYCODE_S = 47;
+ /** Key code constant: 'T' key. */
+ public static final int KEYCODE_T = 48;
+ /** Key code constant: 'U' key. */
+ public static final int KEYCODE_U = 49;
+ /** Key code constant: 'V' key. */
+ public static final int KEYCODE_V = 50;
+ /** Key code constant: 'W' key. */
+ public static final int KEYCODE_W = 51;
+ /** Key code constant: 'X' key. */
+ public static final int KEYCODE_X = 52;
+ /** Key code constant: 'Y' key. */
+ public static final int KEYCODE_Y = 53;
+ /** Key code constant: 'Z' key. */
+ public static final int KEYCODE_Z = 54;
+ /** Key code constant: ',' key. */
+ public static final int KEYCODE_COMMA = 55;
+ /** Key code constant: '.' key. */
+ public static final int KEYCODE_PERIOD = 56;
+ /** Key code constant: Left Alt modifier key. */
+ public static final int KEYCODE_ALT_LEFT = 57;
+ /** Key code constant: Right Alt modifier key. */
+ public static final int KEYCODE_ALT_RIGHT = 58;
+ /** Key code constant: Left Shift modifier key. */
+ public static final int KEYCODE_SHIFT_LEFT = 59;
+ /** Key code constant: Right Shift modifier key. */
+ public static final int KEYCODE_SHIFT_RIGHT = 60;
+ /** Key code constant: Tab key. */
+ public static final int KEYCODE_TAB = 61;
+ /** Key code constant: Space key. */
+ public static final int KEYCODE_SPACE = 62;
+ /** Key code constant: Symbol modifier key.
+ * Used to enter alternate symbols. */
+ public static final int KEYCODE_SYM = 63;
+ /** Key code constant: Explorer special function key.
+ * Used to launch a browser application. */
+ public static final int KEYCODE_EXPLORER = 64;
+ /** Key code constant: Envelope special function key.
+ * Used to launch a mail application. */
+ public static final int KEYCODE_ENVELOPE = 65;
+ /** Key code constant: Enter key. */
+ public static final int KEYCODE_ENTER = 66;
+ /** Key code constant: Backspace key.
+ * Deletes characters before the insertion point, unlike {@link #KEYCODE_FORWARD_DEL}. */
+ public static final int KEYCODE_DEL = 67;
+ /** Key code constant: '`' (backtick) key. */
+ public static final int KEYCODE_GRAVE = 68;
+ /** Key code constant: '-'. */
+ public static final int KEYCODE_MINUS = 69;
+ /** Key code constant: '=' key. */
+ public static final int KEYCODE_EQUALS = 70;
+ /** Key code constant: '[' key. */
+ public static final int KEYCODE_LEFT_BRACKET = 71;
+ /** Key code constant: ']' key. */
+ public static final int KEYCODE_RIGHT_BRACKET = 72;
+ /** Key code constant: '\' key. */
+ public static final int KEYCODE_BACKSLASH = 73;
+ /** Key code constant: ';' key. */
+ public static final int KEYCODE_SEMICOLON = 74;
+ /** Key code constant: ''' (apostrophe) key. */
+ public static final int KEYCODE_APOSTROPHE = 75;
+ /** Key code constant: '/' key. */
+ public static final int KEYCODE_SLASH = 76;
+ /** Key code constant: '@' key. */
+ public static final int KEYCODE_AT = 77;
+ /** Key code constant: Number modifier key.
+ * Used to enter numeric symbols.
+ * This key is not Num Lock; it is more like {@link #KEYCODE_ALT_LEFT} and is
+ * interpreted as an ALT key by {@link android.text.method.MetaKeyKeyListener}. */
+ public static final int KEYCODE_NUM = 78;
+ /** Key code constant: Headset Hook key.
+ * Used to hang up calls and stop media. */
+ public static final int KEYCODE_HEADSETHOOK = 79;
+ /** Key code constant: Camera Focus key.
+ * Used to focus the camera. */
+ public static final int KEYCODE_FOCUS = 80; // *Camera* focus
+ /** Key code constant: '+' key. */
+ public static final int KEYCODE_PLUS = 81;
+ /** Key code constant: Menu key. */
+ public static final int KEYCODE_MENU = 82;
+ /** Key code constant: Notification key. */
+ public static final int KEYCODE_NOTIFICATION = 83;
+ /** Key code constant: Search key. */
+ public static final int KEYCODE_SEARCH = 84;
+ /** Key code constant: Play/Pause media key. */
+ public static final int KEYCODE_MEDIA_PLAY_PAUSE= 85;
+ /** Key code constant: Stop media key. */
+ public static final int KEYCODE_MEDIA_STOP = 86;
+ /** Key code constant: Play Next media key. */
+ public static final int KEYCODE_MEDIA_NEXT = 87;
+ /** Key code constant: Play Previous media key. */
+ public static final int KEYCODE_MEDIA_PREVIOUS = 88;
+ /** Key code constant: Rewind media key. */
+ public static final int KEYCODE_MEDIA_REWIND = 89;
+ /** Key code constant: Fast Forward media key. */
+ public static final int KEYCODE_MEDIA_FAST_FORWARD = 90;
+ /** Key code constant: Mute key.
+ * Mutes the microphone, unlike {@link #KEYCODE_VOLUME_MUTE}. */
+ public static final int KEYCODE_MUTE = 91;
+ /** Key code constant: Page Up key. */
+ public static final int KEYCODE_PAGE_UP = 92;
+ /** Key code constant: Page Down key. */
+ public static final int KEYCODE_PAGE_DOWN = 93;
+ /** Key code constant: Picture Symbols modifier key.
+ * Used to switch symbol sets (Emoji, Kao-moji). */
+ public static final int KEYCODE_PICTSYMBOLS = 94; // switch symbol-sets (Emoji,Kao-moji)
+ /** Key code constant: Switch Charset modifier key.
+ * Used to switch character sets (Kanji, Katakana). */
+ public static final int KEYCODE_SWITCH_CHARSET = 95; // switch char-sets (Kanji,Katakana)
+ /** Key code constant: A Button key.
+ * On a game controller, the A button should be either the button labeled A
+ * or the first button on the bottom row of controller buttons. */
+ public static final int KEYCODE_BUTTON_A = 96;
+ /** Key code constant: B Button key.
+ * On a game controller, the B button should be either the button labeled B
+ * or the second button on the bottom row of controller buttons. */
+ public static final int KEYCODE_BUTTON_B = 97;
+ /** Key code constant: C Button key.
+ * On a game controller, the C button should be either the button labeled C
+ * or the third button on the bottom row of controller buttons. */
+ public static final int KEYCODE_BUTTON_C = 98;
+ /** Key code constant: X Button key.
+ * On a game controller, the X button should be either the button labeled X
+ * or the first button on the upper row of controller buttons. */
+ public static final int KEYCODE_BUTTON_X = 99;
+ /** Key code constant: Y Button key.
+ * On a game controller, the Y button should be either the button labeled Y
+ * or the second button on the upper row of controller buttons. */
+ public static final int KEYCODE_BUTTON_Y = 100;
+ /** Key code constant: Z Button key.
+ * On a game controller, the Z button should be either the button labeled Z
+ * or the third button on the upper row of controller buttons. */
+ public static final int KEYCODE_BUTTON_Z = 101;
+ /** Key code constant: L1 Button key.
+ * On a game controller, the L1 button should be either the button labeled L1 (or L)
+ * or the top left trigger button. */
+ public static final int KEYCODE_BUTTON_L1 = 102;
+ /** Key code constant: R1 Button key.
+ * On a game controller, the R1 button should be either the button labeled R1 (or R)
+ * or the top right trigger button. */
+ public static final int KEYCODE_BUTTON_R1 = 103;
+ /** Key code constant: L2 Button key.
+ * On a game controller, the L2 button should be either the button labeled L2
+ * or the bottom left trigger button. */
+ public static final int KEYCODE_BUTTON_L2 = 104;
+ /** Key code constant: R2 Button key.
+ * On a game controller, the R2 button should be either the button labeled R2
+ * or the bottom right trigger button. */
+ public static final int KEYCODE_BUTTON_R2 = 105;
+ /** Key code constant: Left Thumb Button key.
+ * On a game controller, the left thumb button indicates that the left (or only)
+ * joystick is pressed. */
+ public static final int KEYCODE_BUTTON_THUMBL = 106;
+ /** Key code constant: Right Thumb Button key.
+ * On a game controller, the right thumb button indicates that the right
+ * joystick is pressed. */
+ public static final int KEYCODE_BUTTON_THUMBR = 107;
+ /** Key code constant: Start Button key.
+ * On a game controller, the button labeled Start. */
+ public static final int KEYCODE_BUTTON_START = 108;
+ /** Key code constant: Select Button key.
+ * On a game controller, the button labeled Select. */
+ public static final int KEYCODE_BUTTON_SELECT = 109;
+ /** Key code constant: Mode Button key.
+ * On a game controller, the button labeled Mode. */
+ public static final int KEYCODE_BUTTON_MODE = 110;
+ /** Key code constant: Escape key. */
+ public static final int KEYCODE_ESCAPE = 111;
+ /** Key code constant: Forward Delete key.
+ * Deletes characters ahead of the insertion point, unlike {@link #KEYCODE_DEL}. */
+ public static final int KEYCODE_FORWARD_DEL = 112;
+ /** Key code constant: Left Control modifier key. */
+ public static final int KEYCODE_CTRL_LEFT = 113;
+ /** Key code constant: Right Control modifier key. */
+ public static final int KEYCODE_CTRL_RIGHT = 114;
+ /** Key code constant: Caps Lock key. */
+ public static final int KEYCODE_CAPS_LOCK = 115;
+ /** Key code constant: Scroll Lock key. */
+ public static final int KEYCODE_SCROLL_LOCK = 116;
+ /** Key code constant: Left Meta modifier key. */
+ public static final int KEYCODE_META_LEFT = 117;
+ /** Key code constant: Right Meta modifier key. */
+ public static final int KEYCODE_META_RIGHT = 118;
+ /** Key code constant: Function modifier key. */
+ public static final int KEYCODE_FUNCTION = 119;
+ /** Key code constant: System Request / Print Screen key. */
+ public static final int KEYCODE_SYSRQ = 120;
+ /** Key code constant: Break / Pause key. */
+ public static final int KEYCODE_BREAK = 121;
+ /** Key code constant: Home Movement key.
+ * Used for scrolling or moving the cursor around to the start of a line
+ * or to the top of a list. */
+ public static final int KEYCODE_MOVE_HOME = 122;
+ /** Key code constant: End Movement key.
+ * Used for scrolling or moving the cursor around to the end of a line
+ * or to the bottom of a list. */
+ public static final int KEYCODE_MOVE_END = 123;
+ /** Key code constant: Insert key.
+ * Toggles insert / overwrite edit mode. */
+ public static final int KEYCODE_INSERT = 124;
+ /** Key code constant: Forward key.
+ * Navigates forward in the history stack. Complement of {@link #KEYCODE_BACK}. */
+ public static final int KEYCODE_FORWARD = 125;
+ /** Key code constant: Play media key. */
+ public static final int KEYCODE_MEDIA_PLAY = 126;
+ /** Key code constant: Pause media key. */
+ public static final int KEYCODE_MEDIA_PAUSE = 127;
+ /** Key code constant: Close media key.
+ * May be used to close a CD tray, for example. */
+ public static final int KEYCODE_MEDIA_CLOSE = 128;
+ /** Key code constant: Eject media key.
+ * May be used to eject a CD tray, for example. */
+ public static final int KEYCODE_MEDIA_EJECT = 129;
+ /** Key code constant: Record media key. */
+ public static final int KEYCODE_MEDIA_RECORD = 130;
+ /** Key code constant: F1 key. */
+ public static final int KEYCODE_F1 = 131;
+ /** Key code constant: F2 key. */
+ public static final int KEYCODE_F2 = 132;
+ /** Key code constant: F3 key. */
+ public static final int KEYCODE_F3 = 133;
+ /** Key code constant: F4 key. */
+ public static final int KEYCODE_F4 = 134;
+ /** Key code constant: F5 key. */
+ public static final int KEYCODE_F5 = 135;
+ /** Key code constant: F6 key. */
+ public static final int KEYCODE_F6 = 136;
+ /** Key code constant: F7 key. */
+ public static final int KEYCODE_F7 = 137;
+ /** Key code constant: F8 key. */
+ public static final int KEYCODE_F8 = 138;
+ /** Key code constant: F9 key. */
+ public static final int KEYCODE_F9 = 139;
+ /** Key code constant: F10 key. */
+ public static final int KEYCODE_F10 = 140;
+ /** Key code constant: F11 key. */
+ public static final int KEYCODE_F11 = 141;
+ /** Key code constant: F12 key. */
+ public static final int KEYCODE_F12 = 142;
+ /** Key code constant: Num Lock key.
+ * This is the Num Lock key; it is different from {@link #KEYCODE_NUM}.
+ * This key alters the behavior of other keys on the numeric keypad. */
+ public static final int KEYCODE_NUM_LOCK = 143;
+ /** Key code constant: Numeric keypad '0' key. */
+ public static final int KEYCODE_NUMPAD_0 = 144;
+ /** Key code constant: Numeric keypad '1' key. */
+ public static final int KEYCODE_NUMPAD_1 = 145;
+ /** Key code constant: Numeric keypad '2' key. */
+ public static final int KEYCODE_NUMPAD_2 = 146;
+ /** Key code constant: Numeric keypad '3' key. */
+ public static final int KEYCODE_NUMPAD_3 = 147;
+ /** Key code constant: Numeric keypad '4' key. */
+ public static final int KEYCODE_NUMPAD_4 = 148;
+ /** Key code constant: Numeric keypad '5' key. */
+ public static final int KEYCODE_NUMPAD_5 = 149;
+ /** Key code constant: Numeric keypad '6' key. */
+ public static final int KEYCODE_NUMPAD_6 = 150;
+ /** Key code constant: Numeric keypad '7' key. */
+ public static final int KEYCODE_NUMPAD_7 = 151;
+ /** Key code constant: Numeric keypad '8' key. */
+ public static final int KEYCODE_NUMPAD_8 = 152;
+ /** Key code constant: Numeric keypad '9' key. */
+ public static final int KEYCODE_NUMPAD_9 = 153;
+ /** Key code constant: Numeric keypad '/' key (for division). */
+ public static final int KEYCODE_NUMPAD_DIVIDE = 154;
+ /** Key code constant: Numeric keypad '*' key (for multiplication). */
+ public static final int KEYCODE_NUMPAD_MULTIPLY = 155;
+ /** Key code constant: Numeric keypad '-' key (for subtraction). */
+ public static final int KEYCODE_NUMPAD_SUBTRACT = 156;
+ /** Key code constant: Numeric keypad '+' key (for addition). */
+ public static final int KEYCODE_NUMPAD_ADD = 157;
+ /** Key code constant: Numeric keypad '.' key (for decimals or digit grouping). */
+ public static final int KEYCODE_NUMPAD_DOT = 158;
+ /** Key code constant: Numeric keypad ',' key (for decimals or digit grouping). */
+ public static final int KEYCODE_NUMPAD_COMMA = 159;
+ /** Key code constant: Numeric keypad Enter key. */
+ public static final int KEYCODE_NUMPAD_ENTER = 160;
+ /** Key code constant: Numeric keypad '=' key. */
+ public static final int KEYCODE_NUMPAD_EQUALS = 161;
+ /** Key code constant: Numeric keypad '(' key. */
+ public static final int KEYCODE_NUMPAD_LEFT_PAREN = 162;
+ /** Key code constant: Numeric keypad ')' key. */
+ public static final int KEYCODE_NUMPAD_RIGHT_PAREN = 163;
+ /** Key code constant: Volume Mute key.
+ * Mutes the speaker, unlike {@link #KEYCODE_MUTE}.
+ * This key should normally be implemented as a toggle such that the first press
+ * mutes the speaker and the second press restores the original volume. */
+ public static final int KEYCODE_VOLUME_MUTE = 164;
+ /** Key code constant: Info key.
+ * Common on TV remotes to show additional information related to what is
+ * currently being viewed. */
+ public static final int KEYCODE_INFO = 165;
+ /** Key code constant: Channel up key.
+ * On TV remotes, increments the television channel. */
+ public static final int KEYCODE_CHANNEL_UP = 166;
+ /** Key code constant: Channel down key.
+ * On TV remotes, decrements the television channel. */
+ public static final int KEYCODE_CHANNEL_DOWN = 167;
+ /** Key code constant: Zoom in key. */
+ public static final int KEYCODE_ZOOM_IN = 168;
+ /** Key code constant: Zoom out key. */
+ public static final int KEYCODE_ZOOM_OUT = 169;
+ /** Key code constant: TV key.
+ * On TV remotes, switches to viewing live TV. */
+ public static final int KEYCODE_TV = 170;
+ /** Key code constant: Window key.
+ * On TV remotes, toggles picture-in-picture mode or other windowing functions.
+ * On Android Wear devices, triggers a display offset. */
+ public static final int KEYCODE_WINDOW = 171;
+ /** Key code constant: Guide key.
+ * On TV remotes, shows a programming guide. */
+ public static final int KEYCODE_GUIDE = 172;
+ /** Key code constant: DVR key.
+ * On some TV remotes, switches to a DVR mode for recorded shows. */
+ public static final int KEYCODE_DVR = 173;
+ /** Key code constant: Bookmark key.
+ * On some TV remotes, bookmarks content or web pages. */
+ public static final int KEYCODE_BOOKMARK = 174;
+ /** Key code constant: Toggle captions key.
+ * Switches the mode for closed-captioning text, for example during television shows. */
+ public static final int KEYCODE_CAPTIONS = 175;
+ /** Key code constant: Settings key.
+ * Starts the system settings activity. */
+ public static final int KEYCODE_SETTINGS = 176;
+ /** Key code constant: TV power key.
+ * On TV remotes, toggles the power on a television screen. */
+ public static final int KEYCODE_TV_POWER = 177;
+ /** Key code constant: TV input key.
+ * On TV remotes, switches the input on a television screen. */
+ public static final int KEYCODE_TV_INPUT = 178;
+ /** Key code constant: Set-top-box power key.
+ * On TV remotes, toggles the power on an external Set-top-box. */
+ public static final int KEYCODE_STB_POWER = 179;
+ /** Key code constant: Set-top-box input key.
+ * On TV remotes, switches the input mode on an external Set-top-box. */
+ public static final int KEYCODE_STB_INPUT = 180;
+ /** Key code constant: A/V Receiver power key.
+ * On TV remotes, toggles the power on an external A/V Receiver. */
+ public static final int KEYCODE_AVR_POWER = 181;
+ /** Key code constant: A/V Receiver input key.
+ * On TV remotes, switches the input mode on an external A/V Receiver. */
+ public static final int KEYCODE_AVR_INPUT = 182;
+ /** Key code constant: Red "programmable" key.
+ * On TV remotes, acts as a contextual/programmable key. */
+ public static final int KEYCODE_PROG_RED = 183;
+ /** Key code constant: Green "programmable" key.
+ * On TV remotes, actsas a contextual/programmable key. */
+ public static final int KEYCODE_PROG_GREEN = 184;
+ /** Key code constant: Yellow "programmable" key.
+ * On TV remotes, acts as a contextual/programmable key. */
+ public static final int KEYCODE_PROG_YELLOW = 185;
+ /** Key code constant: Blue "programmable" key.
+ * On TV remotes, acts as a contextual/programmable key. */
+ public static final int KEYCODE_PROG_BLUE = 186;
+ /** Key code constant: App switch key.
+ * Should bring up the application switcher dialog. */
+ public static final int KEYCODE_APP_SWITCH = 187;
+ /** Key code constant: Generic Game Pad Button #1.*/
+ public static final int KEYCODE_BUTTON_1 = 188;
+ /** Key code constant: Generic Game Pad Button #2.*/
+ public static final int KEYCODE_BUTTON_2 = 189;
+ /** Key code constant: Generic Game Pad Button #3.*/
+ public static final int KEYCODE_BUTTON_3 = 190;
+ /** Key code constant: Generic Game Pad Button #4.*/
+ public static final int KEYCODE_BUTTON_4 = 191;
+ /** Key code constant: Generic Game Pad Button #5.*/
+ public static final int KEYCODE_BUTTON_5 = 192;
+ /** Key code constant: Generic Game Pad Button #6.*/
+ public static final int KEYCODE_BUTTON_6 = 193;
+ /** Key code constant: Generic Game Pad Button #7.*/
+ public static final int KEYCODE_BUTTON_7 = 194;
+ /** Key code constant: Generic Game Pad Button #8.*/
+ public static final int KEYCODE_BUTTON_8 = 195;
+ /** Key code constant: Generic Game Pad Button #9.*/
+ public static final int KEYCODE_BUTTON_9 = 196;
+ /** Key code constant: Generic Game Pad Button #10.*/
+ public static final int KEYCODE_BUTTON_10 = 197;
+ /** Key code constant: Generic Game Pad Button #11.*/
+ public static final int KEYCODE_BUTTON_11 = 198;
+ /** Key code constant: Generic Game Pad Button #12.*/
+ public static final int KEYCODE_BUTTON_12 = 199;
+ /** Key code constant: Generic Game Pad Button #13.*/
+ public static final int KEYCODE_BUTTON_13 = 200;
+ /** Key code constant: Generic Game Pad Button #14.*/
+ public static final int KEYCODE_BUTTON_14 = 201;
+ /** Key code constant: Generic Game Pad Button #15.*/
+ public static final int KEYCODE_BUTTON_15 = 202;
+ /** Key code constant: Generic Game Pad Button #16.*/
+ public static final int KEYCODE_BUTTON_16 = 203;
+ /** Key code constant: Language Switch key.
+ * Toggles the current input language such as switching between English and Japanese on
+ * a QWERTY keyboard. On some devices, the same function may be performed by
+ * pressing Shift+Spacebar. */
+ public static final int KEYCODE_LANGUAGE_SWITCH = 204;
+ /** Key code constant: Manner Mode key.
+ * Toggles silent or vibrate mode on and off to make the device behave more politely
+ * in certain settings such as on a crowded train. On some devices, the key may only
+ * operate when long-pressed. */
+ public static final int KEYCODE_MANNER_MODE = 205;
+ /** Key code constant: 3D Mode key.
+ * Toggles the display between 2D and 3D mode. */
+ public static final int KEYCODE_3D_MODE = 206;
+ /** Key code constant: Contacts special function key.
+ * Used to launch an address book application. */
+ public static final int KEYCODE_CONTACTS = 207;
+ /** Key code constant: Calendar special function key.
+ * Used to launch a calendar application. */
+ public static final int KEYCODE_CALENDAR = 208;
+ /** Key code constant: Music special function key.
+ * Used to launch a music player application. */
+ public static final int KEYCODE_MUSIC = 209;
+ /** Key code constant: Calculator special function key.
+ * Used to launch a calculator application. */
+ public static final int KEYCODE_CALCULATOR = 210;
+ /** Key code constant: Japanese full-width / half-width key. */
+ public static final int KEYCODE_ZENKAKU_HANKAKU = 211;
+ /** Key code constant: Japanese alphanumeric key. */
+ public static final int KEYCODE_EISU = 212;
+ /** Key code constant: Japanese non-conversion key. */
+ public static final int KEYCODE_MUHENKAN = 213;
+ /** Key code constant: Japanese conversion key. */
+ public static final int KEYCODE_HENKAN = 214;
+ /** Key code constant: Japanese katakana / hiragana key. */
+ public static final int KEYCODE_KATAKANA_HIRAGANA = 215;
+ /** Key code constant: Japanese Yen key. */
+ public static final int KEYCODE_YEN = 216;
+ /** Key code constant: Japanese Ro key. */
+ public static final int KEYCODE_RO = 217;
+ /** Key code constant: Japanese kana key. */
+ public static final int KEYCODE_KANA = 218;
+ /** Key code constant: Assist key.
+ * Launches the global assist activity. Not delivered to applications. */
+ public static final int KEYCODE_ASSIST = 219;
+ /** Key code constant: Brightness Down key.
+ * Adjusts the screen brightness down. */
+ public static final int KEYCODE_BRIGHTNESS_DOWN = 220;
+ /** Key code constant: Brightness Up key.
+ * Adjusts the screen brightness up. */
+ public static final int KEYCODE_BRIGHTNESS_UP = 221;
+ /** Key code constant: Audio Track key.
+ * Switches the audio tracks. */
+ public static final int KEYCODE_MEDIA_AUDIO_TRACK = 222;
+ /** Key code constant: Sleep key.
+ * Puts the device to sleep. Behaves somewhat like {@link #KEYCODE_POWER} but it
+ * has no effect if the device is already asleep. */
+ public static final int KEYCODE_SLEEP = 223;
+ /** Key code constant: Wakeup key.
+ * Wakes up the device. Behaves somewhat like {@link #KEYCODE_POWER} but it
+ * has no effect if the device is already awake. */
+ public static final int KEYCODE_WAKEUP = 224;
+ /** Key code constant: Pairing key.
+ * Initiates peripheral pairing mode. Useful for pairing remote control
+ * devices or game controllers, especially if no other input mode is
+ * available. */
+ public static final int KEYCODE_PAIRING = 225;
+ /** Key code constant: Media Top Menu key.
+ * Goes to the top of media menu. */
+ public static final int KEYCODE_MEDIA_TOP_MENU = 226;
+ /** Key code constant: '11' key. */
+ public static final int KEYCODE_11 = 227;
+ /** Key code constant: '12' key. */
+ public static final int KEYCODE_12 = 228;
+ /** Key code constant: Last Channel key.
+ * Goes to the last viewed channel. */
+ public static final int KEYCODE_LAST_CHANNEL = 229;
+ /** Key code constant: TV data service key.
+ * Displays data services like weather, sports. */
+ public static final int KEYCODE_TV_DATA_SERVICE = 230;
+ /** Key code constant: Voice Assist key.
+ * Launches the global voice assist activity. Not delivered to applications. */
+ public static final int KEYCODE_VOICE_ASSIST = 231;
+ /** Key code constant: Radio key.
+ * Toggles TV service / Radio service. */
+ public static final int KEYCODE_TV_RADIO_SERVICE = 232;
+ /** Key code constant: Teletext key.
+ * Displays Teletext service. */
+ public static final int KEYCODE_TV_TELETEXT = 233;
+ /** Key code constant: Number entry key.
+ * Initiates to enter multi-digit channel nubmber when each digit key is assigned
+ * for selecting separate channel. Corresponds to Number Entry Mode (0x1D) of CEC
+ * User Control Code. */
+ public static final int KEYCODE_TV_NUMBER_ENTRY = 234;
+ /** Key code constant: Analog Terrestrial key.
+ * Switches to analog terrestrial broadcast service. */
+ public static final int KEYCODE_TV_TERRESTRIAL_ANALOG = 235;
+ /** Key code constant: Digital Terrestrial key.
+ * Switches to digital terrestrial broadcast service. */
+ public static final int KEYCODE_TV_TERRESTRIAL_DIGITAL = 236;
+ /** Key code constant: Satellite key.
+ * Switches to digital satellite broadcast service. */
+ public static final int KEYCODE_TV_SATELLITE = 237;
+ /** Key code constant: BS key.
+ * Switches to BS digital satellite broadcasting service available in Japan. */
+ public static final int KEYCODE_TV_SATELLITE_BS = 238;
+ /** Key code constant: CS key.
+ * Switches to CS digital satellite broadcasting service available in Japan. */
+ public static final int KEYCODE_TV_SATELLITE_CS = 239;
+ /** Key code constant: BS/CS key.
+ * Toggles between BS and CS digital satellite services. */
+ public static final int KEYCODE_TV_SATELLITE_SERVICE = 240;
+ /** Key code constant: Toggle Network key.
+ * Toggles selecting broacast services. */
+ public static final int KEYCODE_TV_NETWORK = 241;
+ /** Key code constant: Antenna/Cable key.
+ * Toggles broadcast input source between antenna and cable. */
+ public static final int KEYCODE_TV_ANTENNA_CABLE = 242;
+ /** Key code constant: HDMI #1 key.
+ * Switches to HDMI input #1. */
+ public static final int KEYCODE_TV_INPUT_HDMI_1 = 243;
+ /** Key code constant: HDMI #2 key.
+ * Switches to HDMI input #2. */
+ public static final int KEYCODE_TV_INPUT_HDMI_2 = 244;
+ /** Key code constant: HDMI #3 key.
+ * Switches to HDMI input #3. */
+ public static final int KEYCODE_TV_INPUT_HDMI_3 = 245;
+ /** Key code constant: HDMI #4 key.
+ * Switches to HDMI input #4. */
+ public static final int KEYCODE_TV_INPUT_HDMI_4 = 246;
+ /** Key code constant: Composite #1 key.
+ * Switches to composite video input #1. */
+ public static final int KEYCODE_TV_INPUT_COMPOSITE_1 = 247;
+ /** Key code constant: Composite #2 key.
+ * Switches to composite video input #2. */
+ public static final int KEYCODE_TV_INPUT_COMPOSITE_2 = 248;
+ /** Key code constant: Component #1 key.
+ * Switches to component video input #1. */
+ public static final int KEYCODE_TV_INPUT_COMPONENT_1 = 249;
+ /** Key code constant: Component #2 key.
+ * Switches to component video input #2. */
+ public static final int KEYCODE_TV_INPUT_COMPONENT_2 = 250;
+ /** Key code constant: VGA #1 key.
+ * Switches to VGA (analog RGB) input #1. */
+ public static final int KEYCODE_TV_INPUT_VGA_1 = 251;
+ /** Key code constant: Audio description key.
+ * Toggles audio description off / on. */
+ public static final int KEYCODE_TV_AUDIO_DESCRIPTION = 252;
+ /** Key code constant: Audio description mixing volume up key.
+ * Louden audio description volume as compared with normal audio volume. */
+ public static final int KEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP = 253;
+ /** Key code constant: Audio description mixing volume down key.
+ * Lessen audio description volume as compared with normal audio volume. */
+ public static final int KEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN = 254;
+ /** Key code constant: Zoom mode key.
+ * Changes Zoom mode (Normal, Full, Zoom, Wide-zoom, etc.) */
+ public static final int KEYCODE_TV_ZOOM_MODE = 255;
+ /** Key code constant: Contents menu key.
+ * Goes to the title list. Corresponds to Contents Menu (0x0B) of CEC User Control
+ * Code */
+ public static final int KEYCODE_TV_CONTENTS_MENU = 256;
+ /** Key code constant: Media context menu key.
+ * Goes to the context menu of media contents. Corresponds to Media Context-sensitive
+ * Menu (0x11) of CEC User Control Code. */
+ public static final int KEYCODE_TV_MEDIA_CONTEXT_MENU = 257;
+ /** Key code constant: Timer programming key.
+ * Goes to the timer recording menu. Corresponds to Timer Programming (0x54) of
+ * CEC User Control Code. */
+ public static final int KEYCODE_TV_TIMER_PROGRAMMING = 258;
+ /** Key code constant: Help key. */
+ public static final int KEYCODE_HELP = 259;
+ /** Key code constant: Navigate to previous key.
+ * Goes backward by one item in an ordered collection of items. */
+ public static final int KEYCODE_NAVIGATE_PREVIOUS = 260;
+ /** Key code constant: Navigate to next key.
+ * Advances to the next item in an ordered collection of items. */
+ public static final int KEYCODE_NAVIGATE_NEXT = 261;
+ /** Key code constant: Navigate in key.
+ * Activates the item that currently has focus or expands to the next level of a navigation
+ * hierarchy. */
+ public static final int KEYCODE_NAVIGATE_IN = 262;
+ /** Key code constant: Navigate out key.
+ * Backs out one level of a navigation hierarchy or collapses the item that currently has
+ * focus. */
+ public static final int KEYCODE_NAVIGATE_OUT = 263;
+ /** Key code constant: Primary stem key for Wear
+ * Main power/reset button on watch. */
+ public static final int KEYCODE_STEM_PRIMARY = 264;
+ /** Key code constant: Generic stem key 1 for Wear */
+ public static final int KEYCODE_STEM_1 = 265;
+ /** Key code constant: Generic stem key 2 for Wear */
+ public static final int KEYCODE_STEM_2 = 266;
+ /** Key code constant: Generic stem key 3 for Wear */
+ public static final int KEYCODE_STEM_3 = 267;
+ /** Key code constant: Directional Pad Up-Left */
+ public static final int KEYCODE_DPAD_UP_LEFT = 268;
+ /** Key code constant: Directional Pad Down-Left */
+ public static final int KEYCODE_DPAD_DOWN_LEFT = 269;
+ /** Key code constant: Directional Pad Up-Right */
+ public static final int KEYCODE_DPAD_UP_RIGHT = 270;
+ /** Key code constant: Directional Pad Down-Right */
+ public static final int KEYCODE_DPAD_DOWN_RIGHT = 271;
+ /** Key code constant: Skip forward media key. */
+ public static final int KEYCODE_MEDIA_SKIP_FORWARD = 272;
+ /** Key code constant: Skip backward media key. */
+ public static final int KEYCODE_MEDIA_SKIP_BACKWARD = 273;
+ /** Key code constant: Step forward media key.
+ * Steps media forward, one frame at a time. */
+ public static final int KEYCODE_MEDIA_STEP_FORWARD = 274;
+ /** Key code constant: Step backward media key.
+ * Steps media backward, one frame at a time. */
+ public static final int KEYCODE_MEDIA_STEP_BACKWARD = 275;
+ /** Key code constant: put device to sleep unless a wakelock is held. */
+ public static final int KEYCODE_SOFT_SLEEP = 276;
+ /** Key code constant: Cut key. */
+ public static final int KEYCODE_CUT = 277;
+ /** Key code constant: Copy key. */
+ public static final int KEYCODE_COPY = 278;
+ /** Key code constant: Paste key. */
+ public static final int KEYCODE_PASTE = 279;
+ /** Key code constant: Consumed by the system for navigation up */
+ public static final int KEYCODE_SYSTEM_NAVIGATION_UP = 280;
+ /** Key code constant: Consumed by the system for navigation down */
+ public static final int KEYCODE_SYSTEM_NAVIGATION_DOWN = 281;
+ /** Key code constant: Consumed by the system for navigation left*/
+ public static final int KEYCODE_SYSTEM_NAVIGATION_LEFT = 282;
+ /** Key code constant: Consumed by the system for navigation right */
+ public static final int KEYCODE_SYSTEM_NAVIGATION_RIGHT = 283;
+ /** Key code constant: Show all apps */
+ public static final int KEYCODE_ALL_APPS = 284;
+ /** Key code constant: Refresh key. */
+ public static final int KEYCODE_REFRESH = 285;
+ /** Key code constant: Thumbs up key. Apps can use this to let user upvote content. */
+ public static final int KEYCODE_THUMBS_UP = 286;
+ /** Key code constant: Thumbs down key. Apps can use this to let user downvote content. */
+ public static final int KEYCODE_THUMBS_DOWN = 287;
+ /**
+ * Key code constant: Used to switch current {@link android.accounts.Account} that is
+ * consuming content. May be consumed by system to set account globally.
+ */
+ public static final int KEYCODE_PROFILE_SWITCH = 288;
+
+ /**
+ * Integer value of the last KEYCODE. Increases as new keycodes are added to KeyEvent.
+ * @hide
+ */
+ @TestApi
+ public static final int LAST_KEYCODE = KEYCODE_PROFILE_SWITCH;
+
+ // NOTE: If you add a new keycode here you must also add it to:
+ // isSystem()
+ // isWakeKey()
+ // frameworks/native/include/android/keycodes.h
+ // frameworks/native/include/input/InputEventLabels.h
+ // frameworks/base/core/res/res/values/attrs.xml
+ // emulator?
+ // LAST_KEYCODE
+ //
+ // Also Android currently does not reserve code ranges for vendor-
+ // specific key codes. If you have new key codes to have, you
+ // MUST contribute a patch to the open source project to define
+ // those new codes. This is intended to maintain a consistent
+ // set of key code definitions across all Android devices.
+
+ // Symbolic names of all metakeys in bit order from least significant to most significant.
+ // Accordingly there are exactly 32 values in this table.
+ @UnsupportedAppUsage
+ private static final String[] META_SYMBOLIC_NAMES = new String[] {
+ "META_SHIFT_ON",
+ "META_ALT_ON",
+ "META_SYM_ON",
+ "META_FUNCTION_ON",
+ "META_ALT_LEFT_ON",
+ "META_ALT_RIGHT_ON",
+ "META_SHIFT_LEFT_ON",
+ "META_SHIFT_RIGHT_ON",
+ "META_CAP_LOCKED",
+ "META_ALT_LOCKED",
+ "META_SYM_LOCKED",
+ "0x00000800",
+ "META_CTRL_ON",
+ "META_CTRL_LEFT_ON",
+ "META_CTRL_RIGHT_ON",
+ "0x00008000",
+ "META_META_ON",
+ "META_META_LEFT_ON",
+ "META_META_RIGHT_ON",
+ "0x00080000",
+ "META_CAPS_LOCK_ON",
+ "META_NUM_LOCK_ON",
+ "META_SCROLL_LOCK_ON",
+ "0x00800000",
+ "0x01000000",
+ "0x02000000",
+ "0x04000000",
+ "0x08000000",
+ "0x10000000",
+ "0x20000000",
+ "0x40000000",
+ "0x80000000",
+ };
+
+ private static final String LABEL_PREFIX = "KEYCODE_";
+
+ /**
+ * @deprecated There are now more than MAX_KEYCODE keycodes.
+ * Use {@link #getMaxKeyCode()} instead.
+ */
+ @Deprecated
+ public static final int MAX_KEYCODE = 84;
+
+ /**
+ * {@link #getAction} value: the key has been pressed down.
+ */
+ public static final int ACTION_DOWN = 0;
+ /**
+ * {@link #getAction} value: the key has been released.
+ */
+ public static final int ACTION_UP = 1;
+ /**
+ * @deprecated No longer used by the input system.
+ * {@link #getAction} value: multiple duplicate key events have
+ * occurred in a row, or a complex string is being delivered. If the
+ * key code is not {@link #KEYCODE_UNKNOWN} then the
+ * {@link #getRepeatCount()} method returns the number of times
+ * the given key code should be executed.
+ * Otherwise, if the key code is {@link #KEYCODE_UNKNOWN}, then
+ * this is a sequence of characters as returned by {@link #getCharacters}.
+ */
+ @Deprecated
+ public static final int ACTION_MULTIPLE = 2;
+
+ /**
+ * SHIFT key locked in CAPS mode.
+ * Reserved for use by {@link MetaKeyKeyListener} for a published constant in its API.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static final int META_CAP_LOCKED = 0x100;
+
+ /**
+ * ALT key locked.
+ * Reserved for use by {@link MetaKeyKeyListener} for a published constant in its API.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static final int META_ALT_LOCKED = 0x200;
+
+ /**
+ * SYM key locked.
+ * Reserved for use by {@link MetaKeyKeyListener} for a published constant in its API.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static final int META_SYM_LOCKED = 0x400;
+
+ /**
+ * Text is in selection mode.
+ * Reserved for use by {@link MetaKeyKeyListener} for a private unpublished constant
+ * in its API that is currently being retained for legacy reasons.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static final int META_SELECTING = 0x800;
+
+ /**
+ * <p>This mask is used to check whether one of the ALT meta keys is pressed.</p>
+ *
+ * @see #isAltPressed()
+ * @see #getMetaState()
+ * @see #KEYCODE_ALT_LEFT
+ * @see #KEYCODE_ALT_RIGHT
+ */
+ public static final int META_ALT_ON = 0x02;
+
+ /**
+ * <p>This mask is used to check whether the left ALT meta key is pressed.</p>
+ *
+ * @see #isAltPressed()
+ * @see #getMetaState()
+ * @see #KEYCODE_ALT_LEFT
+ */
+ public static final int META_ALT_LEFT_ON = 0x10;
+
+ /**
+ * <p>This mask is used to check whether the right the ALT meta key is pressed.</p>
+ *
+ * @see #isAltPressed()
+ * @see #getMetaState()
+ * @see #KEYCODE_ALT_RIGHT
+ */
+ public static final int META_ALT_RIGHT_ON = 0x20;
+
+ /**
+ * <p>This mask is used to check whether one of the SHIFT meta keys is pressed.</p>
+ *
+ * @see #isShiftPressed()
+ * @see #getMetaState()
+ * @see #KEYCODE_SHIFT_LEFT
+ * @see #KEYCODE_SHIFT_RIGHT
+ */
+ public static final int META_SHIFT_ON = 0x1;
+
+ /**
+ * <p>This mask is used to check whether the left SHIFT meta key is pressed.</p>
+ *
+ * @see #isShiftPressed()
+ * @see #getMetaState()
+ * @see #KEYCODE_SHIFT_LEFT
+ */
+ public static final int META_SHIFT_LEFT_ON = 0x40;
+
+ /**
+ * <p>This mask is used to check whether the right SHIFT meta key is pressed.</p>
+ *
+ * @see #isShiftPressed()
+ * @see #getMetaState()
+ * @see #KEYCODE_SHIFT_RIGHT
+ */
+ public static final int META_SHIFT_RIGHT_ON = 0x80;
+
+ /**
+ * <p>This mask is used to check whether the SYM meta key is pressed.</p>
+ *
+ * @see #isSymPressed()
+ * @see #getMetaState()
+ */
+ public static final int META_SYM_ON = 0x4;
+
+ /**
+ * <p>This mask is used to check whether the FUNCTION meta key is pressed.</p>
+ *
+ * @see #isFunctionPressed()
+ * @see #getMetaState()
+ */
+ public static final int META_FUNCTION_ON = 0x8;
+
+ /**
+ * <p>This mask is used to check whether one of the CTRL meta keys is pressed.</p>
+ *
+ * @see #isCtrlPressed()
+ * @see #getMetaState()
+ * @see #KEYCODE_CTRL_LEFT
+ * @see #KEYCODE_CTRL_RIGHT
+ */
+ public static final int META_CTRL_ON = 0x1000;
+
+ /**
+ * <p>This mask is used to check whether the left CTRL meta key is pressed.</p>
+ *
+ * @see #isCtrlPressed()
+ * @see #getMetaState()
+ * @see #KEYCODE_CTRL_LEFT
+ */
+ public static final int META_CTRL_LEFT_ON = 0x2000;
+
+ /**
+ * <p>This mask is used to check whether the right CTRL meta key is pressed.</p>
+ *
+ * @see #isCtrlPressed()
+ * @see #getMetaState()
+ * @see #KEYCODE_CTRL_RIGHT
+ */
+ public static final int META_CTRL_RIGHT_ON = 0x4000;
+
+ /**
+ * <p>This mask is used to check whether one of the META meta keys is pressed.</p>
+ *
+ * @see #isMetaPressed()
+ * @see #getMetaState()
+ * @see #KEYCODE_META_LEFT
+ * @see #KEYCODE_META_RIGHT
+ */
+ public static final int META_META_ON = 0x10000;
+
+ /**
+ * <p>This mask is used to check whether the left META meta key is pressed.</p>
+ *
+ * @see #isMetaPressed()
+ * @see #getMetaState()
+ * @see #KEYCODE_META_LEFT
+ */
+ public static final int META_META_LEFT_ON = 0x20000;
+
+ /**
+ * <p>This mask is used to check whether the right META meta key is pressed.</p>
+ *
+ * @see #isMetaPressed()
+ * @see #getMetaState()
+ * @see #KEYCODE_META_RIGHT
+ */
+ public static final int META_META_RIGHT_ON = 0x40000;
+
+ /**
+ * <p>This mask is used to check whether the CAPS LOCK meta key is on.</p>
+ *
+ * @see #isCapsLockOn()
+ * @see #getMetaState()
+ * @see #KEYCODE_CAPS_LOCK
+ */
+ public static final int META_CAPS_LOCK_ON = 0x100000;
+
+ /**
+ * <p>This mask is used to check whether the NUM LOCK meta key is on.</p>
+ *
+ * @see #isNumLockOn()
+ * @see #getMetaState()
+ * @see #KEYCODE_NUM_LOCK
+ */
+ public static final int META_NUM_LOCK_ON = 0x200000;
+
+ /**
+ * <p>This mask is used to check whether the SCROLL LOCK meta key is on.</p>
+ *
+ * @see #isScrollLockOn()
+ * @see #getMetaState()
+ * @see #KEYCODE_SCROLL_LOCK
+ */
+ public static final int META_SCROLL_LOCK_ON = 0x400000;
+
+ /**
+ * This mask is a combination of {@link #META_SHIFT_ON}, {@link #META_SHIFT_LEFT_ON}
+ * and {@link #META_SHIFT_RIGHT_ON}.
+ */
+ public static final int META_SHIFT_MASK = META_SHIFT_ON
+ | META_SHIFT_LEFT_ON | META_SHIFT_RIGHT_ON;
+
+ /**
+ * This mask is a combination of {@link #META_ALT_ON}, {@link #META_ALT_LEFT_ON}
+ * and {@link #META_ALT_RIGHT_ON}.
+ */
+ public static final int META_ALT_MASK = META_ALT_ON
+ | META_ALT_LEFT_ON | META_ALT_RIGHT_ON;
+
+ /**
+ * This mask is a combination of {@link #META_CTRL_ON}, {@link #META_CTRL_LEFT_ON}
+ * and {@link #META_CTRL_RIGHT_ON}.
+ */
+ public static final int META_CTRL_MASK = META_CTRL_ON
+ | META_CTRL_LEFT_ON | META_CTRL_RIGHT_ON;
+
+ /**
+ * This mask is a combination of {@link #META_META_ON}, {@link #META_META_LEFT_ON}
+ * and {@link #META_META_RIGHT_ON}.
+ */
+ public static final int META_META_MASK = META_META_ON
+ | META_META_LEFT_ON | META_META_RIGHT_ON;
+
+ /**
+ * This mask is set if the device woke because of this key event.
+ *
+ * @deprecated This flag will never be set by the system since the system
+ * consumes all wake keys itself.
+ */
+ @Deprecated
+ public static final int FLAG_WOKE_HERE = 0x1;
+
+ /**
+ * This mask is set if the key event was generated by a software keyboard.
+ */
+ public static final int FLAG_SOFT_KEYBOARD = 0x2;
+
+ /**
+ * This mask is set if we don't want the key event to cause us to leave
+ * touch mode.
+ */
+ public static final int FLAG_KEEP_TOUCH_MODE = 0x4;
+
+ /**
+ * This mask is set if an event was known to come from a trusted part
+ * of the system. That is, the event is known to come from the user,
+ * and could not have been spoofed by a third party component.
+ */
+ public static final int FLAG_FROM_SYSTEM = 0x8;
+
+ /**
+ * This mask is used for compatibility, to identify enter keys that are
+ * coming from an IME whose enter key has been auto-labelled "next" or
+ * "done". This allows TextView to dispatch these as normal enter keys
+ * for old applications, but still do the appropriate action when
+ * receiving them.
+ */
+ public static final int FLAG_EDITOR_ACTION = 0x10;
+
+ /**
+ * When associated with up key events, this indicates that the key press
+ * has been canceled. Typically this is used with virtual touch screen
+ * keys, where the user can slide from the virtual key area on to the
+ * display: in that case, the application will receive a canceled up
+ * event and should not perform the action normally associated with the
+ * key. Note that for this to work, the application can not perform an
+ * action for a key until it receives an up or the long press timeout has
+ * expired.
+ */
+ public static final int FLAG_CANCELED = 0x20;
+
+ /**
+ * This key event was generated by a virtual (on-screen) hard key area.
+ * Typically this is an area of the touchscreen, outside of the regular
+ * display, dedicated to "hardware" buttons.
+ */
+ public static final int FLAG_VIRTUAL_HARD_KEY = 0x40;
+
+ /**
+ * This flag is set for the first key repeat that occurs after the
+ * long press timeout.
+ */
+ public static final int FLAG_LONG_PRESS = 0x80;
+
+ /**
+ * Set when a key event has {@link #FLAG_CANCELED} set because a long
+ * press action was executed while it was down.
+ */
+ public static final int FLAG_CANCELED_LONG_PRESS = 0x100;
+
+ /**
+ * Set for {@link #ACTION_UP} when this event's key code is still being
+ * tracked from its initial down. That is, somebody requested that tracking
+ * started on the key down and a long press has not caused
+ * the tracking to be canceled.
+ */
+ public static final int FLAG_TRACKING = 0x200;
+
+ /**
+ * Set when a key event has been synthesized to implement default behavior
+ * for an event that the application did not handle.
+ * Fallback key events are generated by unhandled trackball motions
+ * (to emulate a directional keypad) and by certain unhandled key presses
+ * that are declared in the key map (such as special function numeric keypad
+ * keys when numlock is off).
+ */
+ public static final int FLAG_FALLBACK = 0x400;
+
+ /**
+ * Signifies that the key is being predispatched.
+ * @hide
+ */
+ public static final int FLAG_PREDISPATCH = 0x20000000;
+
+ /**
+ * Private control to determine when an app is tracking a key sequence.
+ * @hide
+ */
+ public static final int FLAG_START_TRACKING = 0x40000000;
+
+ /**
+ * Private flag that indicates when the system has detected that this key event
+ * may be inconsistent with respect to the sequence of previously delivered key events,
+ * such as when a key up event is sent but the key was not down.
+ *
+ * @hide
+ * @see #isTainted
+ * @see #setTainted
+ */
+ public static final int FLAG_TAINTED = 0x80000000;
+
+ /**
+ * Returns the maximum keycode.
+ */
+ public static int getMaxKeyCode() {
+ return LAST_KEYCODE;
+ }
+
+ /**
+ * Get the character that is produced by putting accent on the character
+ * c.
+ * For example, getDeadChar('`', 'e') returns &egrave;.
+ */
+ public static int getDeadChar(int accent, int c) {
+ return KeyCharacterMap.getDeadChar(accent, c);
+ }
+
+ static final boolean DEBUG = false;
+ static final String TAG = "KeyEvent";
+
+ private static final int MAX_RECYCLED = 10;
+ private static final Object gRecyclerLock = new Object();
+ private static int gRecyclerUsed;
+ private static KeyEvent gRecyclerTop;
+
+ private KeyEvent mNext;
+
+ private int mId;
+ @UnsupportedAppUsage
+ private int mDeviceId;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private int mSource;
+ private int mDisplayId;
+ private @Nullable byte[] mHmac;
+ @UnsupportedAppUsage
+ private int mMetaState;
+ @UnsupportedAppUsage
+ private int mAction;
+ @UnsupportedAppUsage
+ private int mKeyCode;
+ @UnsupportedAppUsage
+ private int mScanCode;
+ @UnsupportedAppUsage
+ private int mRepeatCount;
+ @UnsupportedAppUsage
+ private int mFlags;
+ @UnsupportedAppUsage
+ private long mDownTime;
+ @UnsupportedAppUsage
+ private long mEventTime;
+ @UnsupportedAppUsage
+ private String mCharacters;
+
+ public interface Callback {
+ /**
+ * Called when a key down event has occurred. If you return true,
+ * you can first call {@link KeyEvent#startTracking()
+ * KeyEvent.startTracking()} to have the framework track the event
+ * through its {@link #onKeyUp(int, KeyEvent)} and also call your
+ * {@link #onKeyLongPress(int, KeyEvent)} if it occurs.
+ *
+ * @param keyCode The value in event.getKeyCode().
+ * @param event Description of the key event.
+ *
+ * @return If you handled the event, return true. If you want to allow
+ * the event to be handled by the next receiver, return false.
+ */
+ boolean onKeyDown(int keyCode, KeyEvent event);
+
+ /**
+ * Called when a long press has occurred. If you return true,
+ * the final key up will have {@link KeyEvent#FLAG_CANCELED} and
+ * {@link KeyEvent#FLAG_CANCELED_LONG_PRESS} set. Note that in
+ * order to receive this callback, someone in the event change
+ * <em>must</em> return true from {@link #onKeyDown} <em>and</em>
+ * call {@link KeyEvent#startTracking()} on the event.
+ *
+ * @param keyCode The value in event.getKeyCode().
+ * @param event Description of the key event.
+ *
+ * @return If you handled the event, return true. If you want to allow
+ * the event to be handled by the next receiver, return false.
+ */
+ boolean onKeyLongPress(int keyCode, KeyEvent event);
+
+ /**
+ * Called when a key up event has occurred.
+ *
+ * @param keyCode The value in event.getKeyCode().
+ * @param event Description of the key event.
+ *
+ * @return If you handled the event, return true. If you want to allow
+ * the event to be handled by the next receiver, return false.
+ */
+ boolean onKeyUp(int keyCode, KeyEvent event);
+
+ /**
+ * Called when a user's interaction with an analog control, such as
+ * flinging a trackball, generates simulated down/up events for the same
+ * key multiple times in quick succession.
+ *
+ * @param keyCode The value in event.getKeyCode().
+ * @param count Number of pairs as returned by event.getRepeatCount().
+ * @param event Description of the key event.
+ *
+ * @return If you handled the event, return true. If you want to allow
+ * the event to be handled by the next receiver, return false.
+ */
+ boolean onKeyMultiple(int keyCode, int count, KeyEvent event);
+ }
+
+ private static native String nativeKeyCodeToString(int keyCode);
+ private static native int nativeKeyCodeFromString(String keyCode);
+ private static native int nativeNextId();
+
+ private KeyEvent() {}
+
+ /**
+ * Create a new key event.
+ *
+ * @param action Action code: either {@link #ACTION_DOWN},
+ * {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}.
+ * @param code The key code.
+ */
+ public KeyEvent(int action, int code) {
+ mId = nativeNextId();
+ mAction = action;
+ mKeyCode = code;
+ mRepeatCount = 0;
+ mDeviceId = KeyCharacterMap.VIRTUAL_KEYBOARD;
+ }
+
+ /**
+ * Create a new key event.
+ *
+ * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis})
+ * at which this key code originally went down.
+ * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis})
+ * at which this event happened.
+ * @param action Action code: either {@link #ACTION_DOWN},
+ * {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}.
+ * @param code The key code.
+ * @param repeat A repeat count for down events (> 0 if this is after the
+ * initial down) or event count for multiple events.
+ */
+ public KeyEvent(long downTime, long eventTime, int action,
+ int code, int repeat) {
+ mId = nativeNextId();
+ mDownTime = downTime;
+ mEventTime = eventTime;
+ mAction = action;
+ mKeyCode = code;
+ mRepeatCount = repeat;
+ mDeviceId = KeyCharacterMap.VIRTUAL_KEYBOARD;
+ }
+
+ /**
+ * Create a new key event.
+ *
+ * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis})
+ * at which this key code originally went down.
+ * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis})
+ * at which this event happened.
+ * @param action Action code: either {@link #ACTION_DOWN},
+ * {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}.
+ * @param code The key code.
+ * @param repeat A repeat count for down events (> 0 if this is after the
+ * initial down) or event count for multiple events.
+ * @param metaState Flags indicating which meta keys are currently pressed.
+ */
+ public KeyEvent(long downTime, long eventTime, int action,
+ int code, int repeat, int metaState) {
+ mId = nativeNextId();
+ mDownTime = downTime;
+ mEventTime = eventTime;
+ mAction = action;
+ mKeyCode = code;
+ mRepeatCount = repeat;
+ mMetaState = metaState;
+ mDeviceId = KeyCharacterMap.VIRTUAL_KEYBOARD;
+ }
+
+ /**
+ * Create a new key event.
+ *
+ * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis})
+ * at which this key code originally went down.
+ * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis})
+ * at which this event happened.
+ * @param action Action code: either {@link #ACTION_DOWN},
+ * {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}.
+ * @param code The key code.
+ * @param repeat A repeat count for down events (> 0 if this is after the
+ * initial down) or event count for multiple events.
+ * @param metaState Flags indicating which meta keys are currently pressed.
+ * @param deviceId The device ID that generated the key event.
+ * @param scancode Raw device scan code of the event.
+ */
+ public KeyEvent(long downTime, long eventTime, int action,
+ int code, int repeat, int metaState,
+ int deviceId, int scancode) {
+ mId = nativeNextId();
+ mDownTime = downTime;
+ mEventTime = eventTime;
+ mAction = action;
+ mKeyCode = code;
+ mRepeatCount = repeat;
+ mMetaState = metaState;
+ mDeviceId = deviceId;
+ mScanCode = scancode;
+ }
+
+ /**
+ * Create a new key event.
+ *
+ * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis})
+ * at which this key code originally went down.
+ * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis})
+ * at which this event happened.
+ * @param action Action code: either {@link #ACTION_DOWN},
+ * {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}.
+ * @param code The key code.
+ * @param repeat A repeat count for down events (> 0 if this is after the
+ * initial down) or event count for multiple events.
+ * @param metaState Flags indicating which meta keys are currently pressed.
+ * @param deviceId The device ID that generated the key event.
+ * @param scancode Raw device scan code of the event.
+ * @param flags The flags for this key event
+ */
+ public KeyEvent(long downTime, long eventTime, int action,
+ int code, int repeat, int metaState,
+ int deviceId, int scancode, int flags) {
+ mId = nativeNextId();
+ mDownTime = downTime;
+ mEventTime = eventTime;
+ mAction = action;
+ mKeyCode = code;
+ mRepeatCount = repeat;
+ mMetaState = metaState;
+ mDeviceId = deviceId;
+ mScanCode = scancode;
+ mFlags = flags;
+ }
+
+ /**
+ * Create a new key event.
+ *
+ * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis})
+ * at which this key code originally went down.
+ * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis})
+ * at which this event happened.
+ * @param action Action code: either {@link #ACTION_DOWN},
+ * {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}.
+ * @param code The key code.
+ * @param repeat A repeat count for down events (> 0 if this is after the
+ * initial down) or event count for multiple events.
+ * @param metaState Flags indicating which meta keys are currently pressed.
+ * @param deviceId The device ID that generated the key event.
+ * @param scancode Raw device scan code of the event.
+ * @param flags The flags for this key event
+ * @param source The input source such as {@link InputDevice#SOURCE_KEYBOARD}.
+ */
+ public KeyEvent(long downTime, long eventTime, int action,
+ int code, int repeat, int metaState,
+ int deviceId, int scancode, int flags, int source) {
+ mId = nativeNextId();
+ mDownTime = downTime;
+ mEventTime = eventTime;
+ mAction = action;
+ mKeyCode = code;
+ mRepeatCount = repeat;
+ mMetaState = metaState;
+ mDeviceId = deviceId;
+ mScanCode = scancode;
+ mFlags = flags;
+ mSource = source;
+ mDisplayId = INVALID_DISPLAY;
+ }
+
+ /**
+ * Create a new key event for a string of characters. The key code,
+ * action, repeat count and source will automatically be set to
+ * {@link #KEYCODE_UNKNOWN}, {@link #ACTION_MULTIPLE}, 0, and
+ * {@link InputDevice#SOURCE_KEYBOARD} for you.
+ *
+ * @param time The time (in {@link android.os.SystemClock#uptimeMillis})
+ * at which this event occured.
+ * @param characters The string of characters.
+ * @param deviceId The device ID that generated the key event.
+ * @param flags The flags for this key event
+ */
+ public KeyEvent(long time, String characters, int deviceId, int flags) {
+ mId = nativeNextId();
+ mDownTime = time;
+ mEventTime = time;
+ mCharacters = characters;
+ mAction = ACTION_MULTIPLE;
+ mKeyCode = KEYCODE_UNKNOWN;
+ mRepeatCount = 0;
+ mDeviceId = deviceId;
+ mFlags = flags;
+ mSource = InputDevice.SOURCE_KEYBOARD;
+ mDisplayId = INVALID_DISPLAY;
+ }
+
+ /**
+ * Make an exact copy of an existing key event.
+ */
+ public KeyEvent(KeyEvent origEvent) {
+ mId = origEvent.mId;
+ mDownTime = origEvent.mDownTime;
+ mEventTime = origEvent.mEventTime;
+ mAction = origEvent.mAction;
+ mKeyCode = origEvent.mKeyCode;
+ mRepeatCount = origEvent.mRepeatCount;
+ mMetaState = origEvent.mMetaState;
+ mDeviceId = origEvent.mDeviceId;
+ mSource = origEvent.mSource;
+ mDisplayId = origEvent.mDisplayId;
+ mHmac = origEvent.mHmac == null ? null : origEvent.mHmac.clone();
+ mScanCode = origEvent.mScanCode;
+ mFlags = origEvent.mFlags;
+ mCharacters = origEvent.mCharacters;
+ }
+
+ /**
+ * Copy an existing key event, modifying its time and repeat count.
+ *
+ * @deprecated Use {@link #changeTimeRepeat(KeyEvent, long, int)}
+ * instead.
+ *
+ * @param origEvent The existing event to be copied.
+ * @param eventTime The new event time
+ * (in {@link android.os.SystemClock#uptimeMillis}) of the event.
+ * @param newRepeat The new repeat count of the event.
+ */
+ @Deprecated
+ public KeyEvent(KeyEvent origEvent, long eventTime, int newRepeat) {
+ mId = nativeNextId(); // Not an exact copy so assign a new ID.
+ mDownTime = origEvent.mDownTime;
+ mEventTime = eventTime;
+ mAction = origEvent.mAction;
+ mKeyCode = origEvent.mKeyCode;
+ mRepeatCount = newRepeat;
+ mMetaState = origEvent.mMetaState;
+ mDeviceId = origEvent.mDeviceId;
+ mSource = origEvent.mSource;
+ mDisplayId = origEvent.mDisplayId;
+ mHmac = null; // Don't copy HMAC, it will be invalid because eventTime is changing
+ mScanCode = origEvent.mScanCode;
+ mFlags = origEvent.mFlags;
+ mCharacters = origEvent.mCharacters;
+ }
+
+ private static KeyEvent obtain() {
+ final KeyEvent ev;
+ synchronized (gRecyclerLock) {
+ ev = gRecyclerTop;
+ if (ev == null) {
+ return new KeyEvent();
+ }
+ gRecyclerTop = ev.mNext;
+ gRecyclerUsed -= 1;
+ }
+ ev.mNext = null;
+ ev.prepareForReuse();
+ return ev;
+ }
+
+ /**
+ * Obtains a (potentially recycled) key event. Used by native code to create a Java object.
+ *
+ * @hide
+ */
+ public static KeyEvent obtain(int id, long downTime, long eventTime, int action,
+ int code, int repeat, int metaState,
+ int deviceId, int scancode, int flags, int source, int displayId, @Nullable byte[] hmac,
+ String characters) {
+ KeyEvent ev = obtain();
+ ev.mId = id;
+ ev.mDownTime = downTime;
+ ev.mEventTime = eventTime;
+ ev.mAction = action;
+ ev.mKeyCode = code;
+ ev.mRepeatCount = repeat;
+ ev.mMetaState = metaState;
+ ev.mDeviceId = deviceId;
+ ev.mScanCode = scancode;
+ ev.mFlags = flags;
+ ev.mSource = source;
+ ev.mDisplayId = displayId;
+ ev.mHmac = hmac;
+ ev.mCharacters = characters;
+ return ev;
+ }
+
+ /**
+ * Obtains a (potentially recycled) key event.
+ *
+ * @hide
+ */
+ public static KeyEvent obtain(long downTime, long eventTime, int action,
+ int code, int repeat, int metaState,
+ int deviceId, int scanCode, int flags, int source, int displayId, String characters) {
+ return obtain(nativeNextId(), downTime, eventTime, action, code, repeat, metaState,
+ deviceId, scanCode, flags, source, displayId, null /* hmac */, characters);
+ }
+
+ /**
+ * Obtains a (potentially recycled) key event.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static KeyEvent obtain(long downTime, long eventTime, int action,
+ int code, int repeat, int metaState,
+ int deviceId, int scancode, int flags, int source, String characters) {
+ return obtain(downTime, eventTime, action, code, repeat, metaState, deviceId, scancode,
+ flags, source, INVALID_DISPLAY, characters);
+ }
+
+ /**
+
+ /**
+ * Obtains a (potentially recycled) copy of another key event.
+ *
+ * @hide
+ */
+ public static KeyEvent obtain(KeyEvent other) {
+ KeyEvent ev = obtain();
+ ev.mId = other.mId;
+ ev.mDownTime = other.mDownTime;
+ ev.mEventTime = other.mEventTime;
+ ev.mAction = other.mAction;
+ ev.mKeyCode = other.mKeyCode;
+ ev.mRepeatCount = other.mRepeatCount;
+ ev.mMetaState = other.mMetaState;
+ ev.mDeviceId = other.mDeviceId;
+ ev.mScanCode = other.mScanCode;
+ ev.mFlags = other.mFlags;
+ ev.mSource = other.mSource;
+ ev.mDisplayId = other.mDisplayId;
+ ev.mHmac = other.mHmac == null ? null : other.mHmac.clone();
+ ev.mCharacters = other.mCharacters;
+ return ev;
+ }
+
+ /** @hide */
+ @Override
+ public KeyEvent copy() {
+ return obtain(this);
+ }
+
+ /**
+ * Recycles a key event.
+ * Key events should only be recycled if they are owned by the system since user
+ * code expects them to be essentially immutable, "tracking" notwithstanding.
+ *
+ * @hide
+ */
+ @Override
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public final void recycle() {
+ super.recycle();
+ mCharacters = null;
+
+ synchronized (gRecyclerLock) {
+ if (gRecyclerUsed < MAX_RECYCLED) {
+ gRecyclerUsed++;
+ mNext = gRecyclerTop;
+ gRecyclerTop = this;
+ }
+ }
+ }
+
+ /** @hide */
+ @Override
+ public final void recycleIfNeededAfterDispatch() {
+ // Do nothing.
+ }
+
+ /** @hide */
+ @Override
+ public int getId() {
+ return mId;
+ }
+
+ /**
+ * Create a new key event that is the same as the given one, but whose
+ * event time and repeat count are replaced with the given value.
+ *
+ * @param event The existing event to be copied. This is not modified.
+ * @param eventTime The new event time
+ * (in {@link android.os.SystemClock#uptimeMillis}) of the event.
+ * @param newRepeat The new repeat count of the event.
+ */
+ public static KeyEvent changeTimeRepeat(KeyEvent event, long eventTime,
+ int newRepeat) {
+ return new KeyEvent(event, eventTime, newRepeat);
+ }
+
+ /**
+ * Create a new key event that is the same as the given one, but whose
+ * event time and repeat count are replaced with the given value.
+ *
+ * @param event The existing event to be copied. This is not modified.
+ * @param eventTime The new event time
+ * (in {@link android.os.SystemClock#uptimeMillis}) of the event.
+ * @param newRepeat The new repeat count of the event.
+ * @param newFlags New flags for the event, replacing the entire value
+ * in the original event.
+ */
+ public static KeyEvent changeTimeRepeat(KeyEvent event, long eventTime,
+ int newRepeat, int newFlags) {
+ KeyEvent ret = new KeyEvent(event);
+ ret.mId = nativeNextId(); // Not an exact copy so assign a new ID.
+ ret.mEventTime = eventTime;
+ ret.mRepeatCount = newRepeat;
+ ret.mFlags = newFlags;
+ return ret;
+ }
+
+ /**
+ * Copy an existing key event, modifying its action.
+ *
+ * @param origEvent The existing event to be copied.
+ * @param action The new action code of the event.
+ */
+ private KeyEvent(KeyEvent origEvent, int action) {
+ mId = nativeNextId(); // Not an exact copy so assign a new ID.
+ mDownTime = origEvent.mDownTime;
+ mEventTime = origEvent.mEventTime;
+ mAction = action;
+ mKeyCode = origEvent.mKeyCode;
+ mRepeatCount = origEvent.mRepeatCount;
+ mMetaState = origEvent.mMetaState;
+ mDeviceId = origEvent.mDeviceId;
+ mSource = origEvent.mSource;
+ mDisplayId = origEvent.mDisplayId;
+ mHmac = null; // Don't copy the hmac, it will be invalid since action is changing
+ mScanCode = origEvent.mScanCode;
+ mFlags = origEvent.mFlags;
+ // Don't copy mCharacters, since one way or the other we'll lose it
+ // when changing the action.
+ }
+
+ /**
+ * Create a new key event that is the same as the given one, but whose
+ * action is replaced with the given value.
+ *
+ * @param event The existing event to be copied. This is not modified.
+ * @param action The new action code of the event.
+ */
+ public static KeyEvent changeAction(KeyEvent event, int action) {
+ return new KeyEvent(event, action);
+ }
+
+ /**
+ * Create a new key event that is the same as the given one, but whose
+ * flags are replaced with the given value.
+ *
+ * @param event The existing event to be copied. This is not modified.
+ * @param flags The new flags constant.
+ */
+ public static KeyEvent changeFlags(KeyEvent event, int flags) {
+ event = new KeyEvent(event);
+ event.mId = nativeNextId(); // Not an exact copy so assign a new ID.
+ event.mFlags = flags;
+ return event;
+ }
+
+ /** @hide */
+ @Override
+ public final boolean isTainted() {
+ return (mFlags & FLAG_TAINTED) != 0;
+ }
+
+ /** @hide */
+ @Override
+ public final void setTainted(boolean tainted) {
+ mFlags = tainted ? mFlags | FLAG_TAINTED : mFlags & ~FLAG_TAINTED;
+ }
+
+ /**
+ * Don't use in new code, instead explicitly check
+ * {@link #getAction()}.
+ *
+ * @return If the action is ACTION_DOWN, returns true; else false.
+ *
+ * @deprecated
+ * @hide
+ */
+ @UnsupportedAppUsage
+ @Deprecated public final boolean isDown() {
+ return mAction == ACTION_DOWN;
+ }
+
+ /** Is this a system key? System keys can not be used for menu shortcuts.
+ */
+ public final boolean isSystem() {
+ return isSystemKey(mKeyCode);
+ }
+
+ /** @hide */
+ public final boolean isWakeKey() {
+ return isWakeKey(mKeyCode);
+ }
+
+ /**
+ * Returns true if the specified keycode is a gamepad button.
+ * @return True if the keycode is a gamepad button, such as {@link #KEYCODE_BUTTON_A}.
+ */
+ public static final boolean isGamepadButton(int keyCode) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_BUTTON_A:
+ case KeyEvent.KEYCODE_BUTTON_B:
+ case KeyEvent.KEYCODE_BUTTON_C:
+ case KeyEvent.KEYCODE_BUTTON_X:
+ case KeyEvent.KEYCODE_BUTTON_Y:
+ case KeyEvent.KEYCODE_BUTTON_Z:
+ case KeyEvent.KEYCODE_BUTTON_L1:
+ case KeyEvent.KEYCODE_BUTTON_R1:
+ case KeyEvent.KEYCODE_BUTTON_L2:
+ case KeyEvent.KEYCODE_BUTTON_R2:
+ case KeyEvent.KEYCODE_BUTTON_THUMBL:
+ case KeyEvent.KEYCODE_BUTTON_THUMBR:
+ case KeyEvent.KEYCODE_BUTTON_START:
+ case KeyEvent.KEYCODE_BUTTON_SELECT:
+ case KeyEvent.KEYCODE_BUTTON_MODE:
+ case KeyEvent.KEYCODE_BUTTON_1:
+ case KeyEvent.KEYCODE_BUTTON_2:
+ case KeyEvent.KEYCODE_BUTTON_3:
+ case KeyEvent.KEYCODE_BUTTON_4:
+ case KeyEvent.KEYCODE_BUTTON_5:
+ case KeyEvent.KEYCODE_BUTTON_6:
+ case KeyEvent.KEYCODE_BUTTON_7:
+ case KeyEvent.KEYCODE_BUTTON_8:
+ case KeyEvent.KEYCODE_BUTTON_9:
+ case KeyEvent.KEYCODE_BUTTON_10:
+ case KeyEvent.KEYCODE_BUTTON_11:
+ case KeyEvent.KEYCODE_BUTTON_12:
+ case KeyEvent.KEYCODE_BUTTON_13:
+ case KeyEvent.KEYCODE_BUTTON_14:
+ case KeyEvent.KEYCODE_BUTTON_15:
+ case KeyEvent.KEYCODE_BUTTON_16:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /** Whether key will, by default, trigger a click on the focused view.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static final boolean isConfirmKey(int keyCode) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ case KeyEvent.KEYCODE_ENTER:
+ case KeyEvent.KEYCODE_SPACE:
+ case KeyEvent.KEYCODE_NUMPAD_ENTER:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Returns whether this key will be sent to the
+ * {@link android.media.session.MediaSession.Callback} if not handled.
+ *
+ * @hide
+ */
+ public static final boolean isMediaSessionKey(int keyCode) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_MEDIA_PLAY:
+ case KeyEvent.KEYCODE_MEDIA_PAUSE:
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ case KeyEvent.KEYCODE_MUTE:
+ case KeyEvent.KEYCODE_HEADSETHOOK:
+ case KeyEvent.KEYCODE_MEDIA_STOP:
+ case KeyEvent.KEYCODE_MEDIA_NEXT:
+ case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+ case KeyEvent.KEYCODE_MEDIA_REWIND:
+ case KeyEvent.KEYCODE_MEDIA_RECORD:
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
+ return true;
+ }
+ return false;
+ }
+
+ /** Is this a system key? System keys can not be used for menu shortcuts.
+ * @hide
+ */
+ public static final boolean isSystemKey(int keyCode) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_MENU:
+ case KeyEvent.KEYCODE_SOFT_RIGHT:
+ case KeyEvent.KEYCODE_HOME:
+ case KeyEvent.KEYCODE_BACK:
+ case KeyEvent.KEYCODE_CALL:
+ case KeyEvent.KEYCODE_ENDCALL:
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ case KeyEvent.KEYCODE_VOLUME_MUTE:
+ case KeyEvent.KEYCODE_MUTE:
+ case KeyEvent.KEYCODE_POWER:
+ case KeyEvent.KEYCODE_HEADSETHOOK:
+ case KeyEvent.KEYCODE_MEDIA_PLAY:
+ case KeyEvent.KEYCODE_MEDIA_PAUSE:
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ case KeyEvent.KEYCODE_MEDIA_STOP:
+ case KeyEvent.KEYCODE_MEDIA_NEXT:
+ case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+ case KeyEvent.KEYCODE_MEDIA_REWIND:
+ case KeyEvent.KEYCODE_MEDIA_RECORD:
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
+ case KeyEvent.KEYCODE_CAMERA:
+ case KeyEvent.KEYCODE_FOCUS:
+ case KeyEvent.KEYCODE_SEARCH:
+ case KeyEvent.KEYCODE_BRIGHTNESS_DOWN:
+ case KeyEvent.KEYCODE_BRIGHTNESS_UP:
+ case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK:
+ case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP:
+ case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN:
+ case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT:
+ case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT:
+ return true;
+ }
+
+ return false;
+ }
+
+ /** @hide */
+ public static final boolean isWakeKey(int keyCode) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_CAMERA:
+ case KeyEvent.KEYCODE_FOCUS:
+ case KeyEvent.KEYCODE_MENU:
+ case KeyEvent.KEYCODE_PAIRING:
+ case KeyEvent.KEYCODE_STEM_1:
+ case KeyEvent.KEYCODE_STEM_2:
+ case KeyEvent.KEYCODE_STEM_3:
+ case KeyEvent.KEYCODE_WAKEUP:
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ case KeyEvent.KEYCODE_VOLUME_MUTE:
+ return true;
+ }
+ return false;
+ }
+
+ /** @hide */
+ public static final boolean isMetaKey(int keyCode) {
+ return keyCode == KeyEvent.KEYCODE_META_LEFT || keyCode == KeyEvent.KEYCODE_META_RIGHT;
+ }
+
+ /** @hide */
+ public static final boolean isAltKey(int keyCode) {
+ return keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public final int getDeviceId() {
+ return mDeviceId;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public final int getSource() {
+ return mSource;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public final void setSource(int source) {
+ mSource = source;
+ }
+
+ /** @hide */
+ @Override
+ public final int getDisplayId() {
+ return mDisplayId;
+ }
+
+ /** @hide */
+ @TestApi
+ @Override
+ public final void setDisplayId(int displayId) {
+ mDisplayId = displayId;
+ }
+
+ /**
+ * <p>Returns the state of the meta keys.</p>
+ *
+ * @return an integer in which each bit set to 1 represents a pressed
+ * meta key
+ *
+ * @see #isAltPressed()
+ * @see #isShiftPressed()
+ * @see #isSymPressed()
+ * @see #isCtrlPressed()
+ * @see #isMetaPressed()
+ * @see #isFunctionPressed()
+ * @see #isCapsLockOn()
+ * @see #isNumLockOn()
+ * @see #isScrollLockOn()
+ * @see #META_ALT_ON
+ * @see #META_ALT_LEFT_ON
+ * @see #META_ALT_RIGHT_ON
+ * @see #META_SHIFT_ON
+ * @see #META_SHIFT_LEFT_ON
+ * @see #META_SHIFT_RIGHT_ON
+ * @see #META_SYM_ON
+ * @see #META_FUNCTION_ON
+ * @see #META_CTRL_ON
+ * @see #META_CTRL_LEFT_ON
+ * @see #META_CTRL_RIGHT_ON
+ * @see #META_META_ON
+ * @see #META_META_LEFT_ON
+ * @see #META_META_RIGHT_ON
+ * @see #META_CAPS_LOCK_ON
+ * @see #META_NUM_LOCK_ON
+ * @see #META_SCROLL_LOCK_ON
+ * @see #getModifiers
+ */
+ public final int getMetaState() {
+ return mMetaState;
+ }
+
+ /**
+ * Returns the state of the modifier keys.
+ * <p>
+ * For the purposes of this function, {@link #KEYCODE_CAPS_LOCK},
+ * {@link #KEYCODE_SCROLL_LOCK}, and {@link #KEYCODE_NUM_LOCK} are
+ * not considered modifier keys. Consequently, this function specifically masks out
+ * {@link #META_CAPS_LOCK_ON}, {@link #META_SCROLL_LOCK_ON} and {@link #META_NUM_LOCK_ON}.
+ * </p><p>
+ * The value returned consists of the meta state (from {@link #getMetaState})
+ * normalized using {@link #normalizeMetaState(int)} and then masked with
+ * {@link #getModifierMetaStateMask} so that only valid modifier bits are retained.
+ * </p>
+ *
+ * @return An integer in which each bit set to 1 represents a pressed modifier key.
+ * @see #getMetaState
+ */
+ public final int getModifiers() {
+ return normalizeMetaState(mMetaState) & META_MODIFIER_MASK;
+ }
+
+ /**
+ * Modifies the flags of the event.
+ *
+ * @param newFlags New flags for the event, replacing the entire value.
+ * @hide
+ */
+ public final void setFlags(int newFlags) {
+ mFlags = newFlags;
+ }
+
+ /**
+ * Returns the flags for this key event.
+ *
+ * @see #FLAG_WOKE_HERE
+ */
+ public final int getFlags() {
+ return mFlags;
+ }
+
+ // Mask of all modifier key meta states. Specifically excludes locked keys like caps lock.
+ @UnsupportedAppUsage
+ private static final int META_MODIFIER_MASK =
+ META_SHIFT_ON | META_SHIFT_LEFT_ON | META_SHIFT_RIGHT_ON
+ | META_ALT_ON | META_ALT_LEFT_ON | META_ALT_RIGHT_ON
+ | META_CTRL_ON | META_CTRL_LEFT_ON | META_CTRL_RIGHT_ON
+ | META_META_ON | META_META_LEFT_ON | META_META_RIGHT_ON
+ | META_SYM_ON | META_FUNCTION_ON;
+
+ // Mask of all lock key meta states.
+ @UnsupportedAppUsage
+ private static final int META_LOCK_MASK =
+ META_CAPS_LOCK_ON | META_NUM_LOCK_ON | META_SCROLL_LOCK_ON;
+
+ // Mask of all valid meta states.
+ @UnsupportedAppUsage
+ private static final int META_ALL_MASK = META_MODIFIER_MASK | META_LOCK_MASK;
+
+ // Mask of all synthetic meta states that are reserved for API compatibility with
+ // historical uses in MetaKeyKeyListener.
+ @UnsupportedAppUsage
+ private static final int META_SYNTHETIC_MASK =
+ META_CAP_LOCKED | META_ALT_LOCKED | META_SYM_LOCKED | META_SELECTING;
+
+ // Mask of all meta states that are not valid use in specifying a modifier key.
+ // These bits are known to be used for purposes other than specifying modifiers.
+ @UnsupportedAppUsage
+ private static final int META_INVALID_MODIFIER_MASK =
+ META_LOCK_MASK | META_SYNTHETIC_MASK;
+
+ /**
+ * Gets a mask that includes all valid modifier key meta state bits.
+ * <p>
+ * For the purposes of this function, {@link #KEYCODE_CAPS_LOCK},
+ * {@link #KEYCODE_SCROLL_LOCK}, and {@link #KEYCODE_NUM_LOCK} are
+ * not considered modifier keys. Consequently, the mask specifically excludes
+ * {@link #META_CAPS_LOCK_ON}, {@link #META_SCROLL_LOCK_ON} and {@link #META_NUM_LOCK_ON}.
+ * </p>
+ *
+ * @return The modifier meta state mask which is a combination of
+ * {@link #META_SHIFT_ON}, {@link #META_SHIFT_LEFT_ON}, {@link #META_SHIFT_RIGHT_ON},
+ * {@link #META_ALT_ON}, {@link #META_ALT_LEFT_ON}, {@link #META_ALT_RIGHT_ON},
+ * {@link #META_CTRL_ON}, {@link #META_CTRL_LEFT_ON}, {@link #META_CTRL_RIGHT_ON},
+ * {@link #META_META_ON}, {@link #META_META_LEFT_ON}, {@link #META_META_RIGHT_ON},
+ * {@link #META_SYM_ON}, {@link #META_FUNCTION_ON}.
+ */
+ public static int getModifierMetaStateMask() {
+ return META_MODIFIER_MASK;
+ }
+
+ /**
+ * Returns true if this key code is a modifier key.
+ * <p>
+ * For the purposes of this function, {@link #KEYCODE_CAPS_LOCK},
+ * {@link #KEYCODE_SCROLL_LOCK}, and {@link #KEYCODE_NUM_LOCK} are
+ * not considered modifier keys. Consequently, this function return false
+ * for those keys.
+ * </p>
+ *
+ * @return True if the key code is one of
+ * {@link #KEYCODE_SHIFT_LEFT} {@link #KEYCODE_SHIFT_RIGHT},
+ * {@link #KEYCODE_ALT_LEFT}, {@link #KEYCODE_ALT_RIGHT},
+ * {@link #KEYCODE_CTRL_LEFT}, {@link #KEYCODE_CTRL_RIGHT},
+ * {@link #KEYCODE_META_LEFT}, or {@link #KEYCODE_META_RIGHT},
+ * {@link #KEYCODE_SYM}, {@link #KEYCODE_NUM}, {@link #KEYCODE_FUNCTION}.
+ */
+ public static boolean isModifierKey(int keyCode) {
+ switch (keyCode) {
+ case KEYCODE_SHIFT_LEFT:
+ case KEYCODE_SHIFT_RIGHT:
+ case KEYCODE_ALT_LEFT:
+ case KEYCODE_ALT_RIGHT:
+ case KEYCODE_CTRL_LEFT:
+ case KEYCODE_CTRL_RIGHT:
+ case KEYCODE_META_LEFT:
+ case KEYCODE_META_RIGHT:
+ case KEYCODE_SYM:
+ case KEYCODE_NUM:
+ case KEYCODE_FUNCTION:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Normalizes the specified meta state.
+ * <p>
+ * The meta state is normalized such that if either the left or right modifier meta state
+ * bits are set then the result will also include the universal bit for that modifier.
+ * </p><p>
+ * If the specified meta state contains {@link #META_ALT_LEFT_ON} then
+ * the result will also contain {@link #META_ALT_ON} in addition to {@link #META_ALT_LEFT_ON}
+ * and the other bits that were specified in the input. The same is process is
+ * performed for shift, control and meta.
+ * </p><p>
+ * If the specified meta state contains synthetic meta states defined by
+ * {@link MetaKeyKeyListener}, then those states are translated here and the original
+ * synthetic meta states are removed from the result.
+ * {@link MetaKeyKeyListener#META_CAP_LOCKED} is translated to {@link #META_CAPS_LOCK_ON}.
+ * {@link MetaKeyKeyListener#META_ALT_LOCKED} is translated to {@link #META_ALT_ON}.
+ * {@link MetaKeyKeyListener#META_SYM_LOCKED} is translated to {@link #META_SYM_ON}.
+ * </p><p>
+ * Undefined meta state bits are removed.
+ * </p>
+ *
+ * @param metaState The meta state.
+ * @return The normalized meta state.
+ */
+ public static int normalizeMetaState(int metaState) {
+ if ((metaState & (META_SHIFT_LEFT_ON | META_SHIFT_RIGHT_ON)) != 0) {
+ metaState |= META_SHIFT_ON;
+ }
+ if ((metaState & (META_ALT_LEFT_ON | META_ALT_RIGHT_ON)) != 0) {
+ metaState |= META_ALT_ON;
+ }
+ if ((metaState & (META_CTRL_LEFT_ON | META_CTRL_RIGHT_ON)) != 0) {
+ metaState |= META_CTRL_ON;
+ }
+ if ((metaState & (META_META_LEFT_ON | META_META_RIGHT_ON)) != 0) {
+ metaState |= META_META_ON;
+ }
+ if ((metaState & MetaKeyKeyListener.META_CAP_LOCKED) != 0) {
+ metaState |= META_CAPS_LOCK_ON;
+ }
+ if ((metaState & MetaKeyKeyListener.META_ALT_LOCKED) != 0) {
+ metaState |= META_ALT_ON;
+ }
+ if ((metaState & MetaKeyKeyListener.META_SYM_LOCKED) != 0) {
+ metaState |= META_SYM_ON;
+ }
+ return metaState & META_ALL_MASK;
+ }
+
+ /**
+ * Returns true if no modifiers keys are pressed according to the specified meta state.
+ * <p>
+ * For the purposes of this function, {@link #KEYCODE_CAPS_LOCK},
+ * {@link #KEYCODE_SCROLL_LOCK}, and {@link #KEYCODE_NUM_LOCK} are
+ * not considered modifier keys. Consequently, this function ignores
+ * {@link #META_CAPS_LOCK_ON}, {@link #META_SCROLL_LOCK_ON} and {@link #META_NUM_LOCK_ON}.
+ * </p><p>
+ * The meta state is normalized prior to comparison using {@link #normalizeMetaState(int)}.
+ * </p>
+ *
+ * @param metaState The meta state to consider.
+ * @return True if no modifier keys are pressed.
+ * @see #hasNoModifiers()
+ */
+ public static boolean metaStateHasNoModifiers(int metaState) {
+ return (normalizeMetaState(metaState) & META_MODIFIER_MASK) == 0;
+ }
+
+ /**
+ * Returns true if only the specified modifier keys are pressed according to
+ * the specified meta state. Returns false if a different combination of modifier
+ * keys are pressed.
+ * <p>
+ * For the purposes of this function, {@link #KEYCODE_CAPS_LOCK},
+ * {@link #KEYCODE_SCROLL_LOCK}, and {@link #KEYCODE_NUM_LOCK} are
+ * not considered modifier keys. Consequently, this function ignores
+ * {@link #META_CAPS_LOCK_ON}, {@link #META_SCROLL_LOCK_ON} and {@link #META_NUM_LOCK_ON}.
+ * </p><p>
+ * If the specified modifier mask includes directional modifiers, such as
+ * {@link #META_SHIFT_LEFT_ON}, then this method ensures that the
+ * modifier is pressed on that side.
+ * If the specified modifier mask includes non-directional modifiers, such as
+ * {@link #META_SHIFT_ON}, then this method ensures that the modifier
+ * is pressed on either side.
+ * If the specified modifier mask includes both directional and non-directional modifiers
+ * for the same type of key, such as {@link #META_SHIFT_ON} and {@link #META_SHIFT_LEFT_ON},
+ * then this method throws an illegal argument exception.
+ * </p>
+ *
+ * @param metaState The meta state to consider.
+ * @param modifiers The meta state of the modifier keys to check. May be a combination
+ * of modifier meta states as defined by {@link #getModifierMetaStateMask()}. May be 0 to
+ * ensure that no modifier keys are pressed.
+ * @return True if only the specified modifier keys are pressed.
+ * @throws IllegalArgumentException if the modifiers parameter contains invalid modifiers
+ * @see #hasModifiers
+ */
+ public static boolean metaStateHasModifiers(int metaState, int modifiers) {
+ // Note: For forward compatibility, we allow the parameter to contain meta states
+ // that we do not recognize but we explicitly disallow meta states that
+ // are not valid modifiers.
+ if ((modifiers & META_INVALID_MODIFIER_MASK) != 0) {
+ throw new IllegalArgumentException("modifiers must not contain "
+ + "META_CAPS_LOCK_ON, META_NUM_LOCK_ON, META_SCROLL_LOCK_ON, "
+ + "META_CAP_LOCKED, META_ALT_LOCKED, META_SYM_LOCKED, "
+ + "or META_SELECTING");
+ }
+
+ metaState = normalizeMetaState(metaState) & META_MODIFIER_MASK;
+ metaState = metaStateFilterDirectionalModifiers(metaState, modifiers,
+ META_SHIFT_ON, META_SHIFT_LEFT_ON, META_SHIFT_RIGHT_ON);
+ metaState = metaStateFilterDirectionalModifiers(metaState, modifiers,
+ META_ALT_ON, META_ALT_LEFT_ON, META_ALT_RIGHT_ON);
+ metaState = metaStateFilterDirectionalModifiers(metaState, modifiers,
+ META_CTRL_ON, META_CTRL_LEFT_ON, META_CTRL_RIGHT_ON);
+ metaState = metaStateFilterDirectionalModifiers(metaState, modifiers,
+ META_META_ON, META_META_LEFT_ON, META_META_RIGHT_ON);
+ return metaState == modifiers;
+ }
+
+ private static int metaStateFilterDirectionalModifiers(int metaState,
+ int modifiers, int basic, int left, int right) {
+ final boolean wantBasic = (modifiers & basic) != 0;
+ final int directional = left | right;
+ final boolean wantLeftOrRight = (modifiers & directional) != 0;
+
+ if (wantBasic) {
+ if (wantLeftOrRight) {
+ throw new IllegalArgumentException("modifiers must not contain "
+ + metaStateToString(basic) + " combined with "
+ + metaStateToString(left) + " or " + metaStateToString(right));
+ }
+ return metaState & ~directional;
+ } else if (wantLeftOrRight) {
+ return metaState & ~basic;
+ } else {
+ return metaState;
+ }
+ }
+
+ /**
+ * Returns true if no modifier keys are pressed.
+ * <p>
+ * For the purposes of this function, {@link #KEYCODE_CAPS_LOCK},
+ * {@link #KEYCODE_SCROLL_LOCK}, and {@link #KEYCODE_NUM_LOCK} are
+ * not considered modifier keys. Consequently, this function ignores
+ * {@link #META_CAPS_LOCK_ON}, {@link #META_SCROLL_LOCK_ON} and {@link #META_NUM_LOCK_ON}.
+ * </p><p>
+ * The meta state is normalized prior to comparison using {@link #normalizeMetaState(int)}.
+ * </p>
+ *
+ * @return True if no modifier keys are pressed.
+ * @see #metaStateHasNoModifiers
+ */
+ public final boolean hasNoModifiers() {
+ return metaStateHasNoModifiers(mMetaState);
+ }
+
+ /**
+ * Returns true if only the specified modifiers keys are pressed.
+ * Returns false if a different combination of modifier keys are pressed.
+ * <p>
+ * For the purposes of this function, {@link #KEYCODE_CAPS_LOCK},
+ * {@link #KEYCODE_SCROLL_LOCK}, and {@link #KEYCODE_NUM_LOCK} are
+ * not considered modifier keys. Consequently, this function ignores
+ * {@link #META_CAPS_LOCK_ON}, {@link #META_SCROLL_LOCK_ON} and {@link #META_NUM_LOCK_ON}.
+ * </p><p>
+ * If the specified modifier mask includes directional modifiers, such as
+ * {@link #META_SHIFT_LEFT_ON}, then this method ensures that the
+ * modifier is pressed on that side.
+ * If the specified modifier mask includes non-directional modifiers, such as
+ * {@link #META_SHIFT_ON}, then this method ensures that the modifier
+ * is pressed on either side.
+ * If the specified modifier mask includes both directional and non-directional modifiers
+ * for the same type of key, such as {@link #META_SHIFT_ON} and {@link #META_SHIFT_LEFT_ON},
+ * then this method throws an illegal argument exception.
+ * </p>
+ *
+ * @param modifiers The meta state of the modifier keys to check. May be a combination
+ * of modifier meta states as defined by {@link #getModifierMetaStateMask()}. May be 0 to
+ * ensure that no modifier keys are pressed.
+ * @return True if only the specified modifier keys are pressed.
+ * @throws IllegalArgumentException if the modifiers parameter contains invalid modifiers
+ * @see #metaStateHasModifiers
+ */
+ public final boolean hasModifiers(int modifiers) {
+ return metaStateHasModifiers(mMetaState, modifiers);
+ }
+
+ /**
+ * <p>Returns the pressed state of the ALT meta key.</p>
+ *
+ * @return true if the ALT key is pressed, false otherwise
+ *
+ * @see #KEYCODE_ALT_LEFT
+ * @see #KEYCODE_ALT_RIGHT
+ * @see #META_ALT_ON
+ */
+ public final boolean isAltPressed() {
+ return (mMetaState & META_ALT_ON) != 0;
+ }
+
+ /**
+ * <p>Returns the pressed state of the SHIFT meta key.</p>
+ *
+ * @return true if the SHIFT key is pressed, false otherwise
+ *
+ * @see #KEYCODE_SHIFT_LEFT
+ * @see #KEYCODE_SHIFT_RIGHT
+ * @see #META_SHIFT_ON
+ */
+ public final boolean isShiftPressed() {
+ return (mMetaState & META_SHIFT_ON) != 0;
+ }
+
+ /**
+ * <p>Returns the pressed state of the SYM meta key.</p>
+ *
+ * @return true if the SYM key is pressed, false otherwise
+ *
+ * @see #KEYCODE_SYM
+ * @see #META_SYM_ON
+ */
+ public final boolean isSymPressed() {
+ return (mMetaState & META_SYM_ON) != 0;
+ }
+
+ /**
+ * <p>Returns the pressed state of the CTRL meta key.</p>
+ *
+ * @return true if the CTRL key is pressed, false otherwise
+ *
+ * @see #KEYCODE_CTRL_LEFT
+ * @see #KEYCODE_CTRL_RIGHT
+ * @see #META_CTRL_ON
+ */
+ public final boolean isCtrlPressed() {
+ return (mMetaState & META_CTRL_ON) != 0;
+ }
+
+ /**
+ * <p>Returns the pressed state of the META meta key.</p>
+ *
+ * @return true if the META key is pressed, false otherwise
+ *
+ * @see #KEYCODE_META_LEFT
+ * @see #KEYCODE_META_RIGHT
+ * @see #META_META_ON
+ */
+ public final boolean isMetaPressed() {
+ return (mMetaState & META_META_ON) != 0;
+ }
+
+ /**
+ * <p>Returns the pressed state of the FUNCTION meta key.</p>
+ *
+ * @return true if the FUNCTION key is pressed, false otherwise
+ *
+ * @see #KEYCODE_FUNCTION
+ * @see #META_FUNCTION_ON
+ */
+ public final boolean isFunctionPressed() {
+ return (mMetaState & META_FUNCTION_ON) != 0;
+ }
+
+ /**
+ * <p>Returns the locked state of the CAPS LOCK meta key.</p>
+ *
+ * @return true if the CAPS LOCK key is on, false otherwise
+ *
+ * @see #KEYCODE_CAPS_LOCK
+ * @see #META_CAPS_LOCK_ON
+ */
+ public final boolean isCapsLockOn() {
+ return (mMetaState & META_CAPS_LOCK_ON) != 0;
+ }
+
+ /**
+ * <p>Returns the locked state of the NUM LOCK meta key.</p>
+ *
+ * @return true if the NUM LOCK key is on, false otherwise
+ *
+ * @see #KEYCODE_NUM_LOCK
+ * @see #META_NUM_LOCK_ON
+ */
+ public final boolean isNumLockOn() {
+ return (mMetaState & META_NUM_LOCK_ON) != 0;
+ }
+
+ /**
+ * <p>Returns the locked state of the SCROLL LOCK meta key.</p>
+ *
+ * @return true if the SCROLL LOCK key is on, false otherwise
+ *
+ * @see #KEYCODE_SCROLL_LOCK
+ * @see #META_SCROLL_LOCK_ON
+ */
+ public final boolean isScrollLockOn() {
+ return (mMetaState & META_SCROLL_LOCK_ON) != 0;
+ }
+
+ /**
+ * Retrieve the action of this key event. May be either
+ * {@link #ACTION_DOWN}, {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}.
+ *
+ * @return The event action: ACTION_DOWN, ACTION_UP, or ACTION_MULTIPLE.
+ */
+ public final int getAction() {
+ return mAction;
+ }
+
+ /**
+ * For {@link #ACTION_UP} events, indicates that the event has been
+ * canceled as per {@link #FLAG_CANCELED}.
+ */
+ public final boolean isCanceled() {
+ return (mFlags&FLAG_CANCELED) != 0;
+ }
+
+ /**
+ * Set {@link #FLAG_CANCELED} flag for the key event.
+ *
+ * @hide
+ */
+ @Override
+ public final void cancel() {
+ mFlags |= FLAG_CANCELED;
+ }
+
+ /**
+ * Call this during {@link Callback#onKeyDown} to have the system track
+ * the key through its final up (possibly including a long press). Note
+ * that only one key can be tracked at a time -- if another key down
+ * event is received while a previous one is being tracked, tracking is
+ * stopped on the previous event.
+ */
+ public final void startTracking() {
+ mFlags |= FLAG_START_TRACKING;
+ }
+
+ /**
+ * For {@link #ACTION_UP} events, indicates that the event is still being
+ * tracked from its initial down event as per
+ * {@link #FLAG_TRACKING}.
+ */
+ public final boolean isTracking() {
+ return (mFlags&FLAG_TRACKING) != 0;
+ }
+
+ /**
+ * For {@link #ACTION_DOWN} events, indicates that the event has been
+ * canceled as per {@link #FLAG_LONG_PRESS}.
+ */
+ public final boolean isLongPress() {
+ return (mFlags&FLAG_LONG_PRESS) != 0;
+ }
+
+ /**
+ * Retrieve the key code of the key event. This is the physical key that
+ * was pressed, <em>not</em> the Unicode character.
+ *
+ * @return The key code of the event.
+ */
+ public final int getKeyCode() {
+ return mKeyCode;
+ }
+
+ /**
+ * For the special case of a {@link #ACTION_MULTIPLE} event with key
+ * code of {@link #KEYCODE_UNKNOWN}, this is a raw string of characters
+ * associated with the event. In all other cases it is null.
+ *
+ * @return Returns a String of 1 or more characters associated with
+ * the event.
+ *
+ * @deprecated no longer used by the input system.
+ */
+ @Deprecated
+ public final String getCharacters() {
+ return mCharacters;
+ }
+
+ /**
+ * Retrieve the hardware key id of this key event. These values are not
+ * reliable and vary from device to device.
+ *
+ * {@more}
+ * Mostly this is here for debugging purposes.
+ */
+ public final int getScanCode() {
+ return mScanCode;
+ }
+
+ /**
+ * Retrieve the repeat count of the event. For key down events,
+ * this is the number of times the key has repeated with the first
+ * down starting at 0 and counting up from there. For key up events,
+ * this is always equal to zero. For multiple key events,
+ * this is the number of down/up pairs that have occurred.
+ *
+ * @return The number of times the key has repeated.
+ */
+ public final int getRepeatCount() {
+ return mRepeatCount;
+ }
+
+ /**
+ * Modifies the down time and the event time of the event.
+ *
+ * @param downTime The new down time (in {@link android.os.SystemClock#uptimeMillis}) of the
+ * event.
+ * @param eventTime The new event time (in {@link android.os.SystemClock#uptimeMillis}) of the
+ * event.
+ * @hide
+ */
+ public final void setTime(long downTime, long eventTime) {
+ mDownTime = downTime;
+ mEventTime = eventTime;
+ }
+
+ /**
+ * Retrieve the time of the most recent key down event,
+ * in the {@link android.os.SystemClock#uptimeMillis} time base. If this
+ * is a down event, this will be the same as {@link #getEventTime()}.
+ * Note that when chording keys, this value is the down time of the
+ * most recently pressed key, which may <em>not</em> be the same physical
+ * key of this event.
+ *
+ * @return Returns the most recent key down time, in the
+ * {@link android.os.SystemClock#uptimeMillis} time base
+ */
+ public final long getDownTime() {
+ return mDownTime;
+ }
+
+ /**
+ * Retrieve the time this event occurred,
+ * in the {@link android.os.SystemClock#uptimeMillis} time base.
+ *
+ * @return Returns the time this event occurred,
+ * in the {@link android.os.SystemClock#uptimeMillis} time base.
+ */
+ @Override
+ public final long getEventTime() {
+ return mEventTime;
+ }
+
+ /**
+ * Retrieve the time this event occurred,
+ * in the {@link android.os.SystemClock#uptimeMillis} time base but with
+ * nanosecond (instead of millisecond) precision.
+ * <p>
+ * The value is in nanosecond precision but it may not have nanosecond accuracy.
+ * </p>
+ *
+ * @return Returns the time this event occurred,
+ * in the {@link android.os.SystemClock#uptimeMillis} time base but with
+ * nanosecond (instead of millisecond) precision.
+ *
+ * @hide
+ */
+ @Override
+ public final long getEventTimeNano() {
+ return mEventTime * 1000000L;
+ }
+
+ /**
+ * Renamed to {@link #getDeviceId}.
+ *
+ * @hide
+ * @deprecated use {@link #getDeviceId()} instead.
+ */
+ @Deprecated
+ public final int getKeyboardDevice() {
+ return mDeviceId;
+ }
+
+ /**
+ * Gets the {@link KeyCharacterMap} associated with the keyboard device.
+ *
+ * @return The associated key character map.
+ * @throws {@link KeyCharacterMap.UnavailableException} if the key character map
+ * could not be loaded because it was malformed or the default key character map
+ * is missing from the system.
+ *
+ * @see KeyCharacterMap#load
+ */
+ public final KeyCharacterMap getKeyCharacterMap() {
+ return KeyCharacterMap.load(mDeviceId);
+ }
+
+ /**
+ * Gets the primary character for this key.
+ * In other words, the label that is physically printed on it.
+ *
+ * @return The display label character, or 0 if none (eg. for non-printing keys).
+ */
+ public char getDisplayLabel() {
+ return getKeyCharacterMap().getDisplayLabel(mKeyCode);
+ }
+
+ /**
+ * Gets the Unicode character generated by the specified key and meta
+ * key state combination.
+ * <p>
+ * Returns the Unicode character that the specified key would produce
+ * when the specified meta bits (see {@link MetaKeyKeyListener})
+ * were active.
+ * </p><p>
+ * Returns 0 if the key is not one that is used to type Unicode
+ * characters.
+ * </p><p>
+ * If the return value has bit {@link KeyCharacterMap#COMBINING_ACCENT} set, the
+ * key is a "dead key" that should be combined with another to
+ * actually produce a character -- see {@link KeyCharacterMap#getDeadChar} --
+ * after masking with {@link KeyCharacterMap#COMBINING_ACCENT_MASK}.
+ * </p>
+ *
+ * @return The associated character or combining accent, or 0 if none.
+ */
+ public int getUnicodeChar() {
+ return getUnicodeChar(mMetaState);
+ }
+
+ /**
+ * Gets the Unicode character generated by the specified key and meta
+ * key state combination.
+ * <p>
+ * Returns the Unicode character that the specified key would produce
+ * when the specified meta bits (see {@link MetaKeyKeyListener})
+ * were active.
+ * </p><p>
+ * Returns 0 if the key is not one that is used to type Unicode
+ * characters.
+ * </p><p>
+ * If the return value has bit {@link KeyCharacterMap#COMBINING_ACCENT} set, the
+ * key is a "dead key" that should be combined with another to
+ * actually produce a character -- see {@link KeyCharacterMap#getDeadChar} --
+ * after masking with {@link KeyCharacterMap#COMBINING_ACCENT_MASK}.
+ * </p>
+ *
+ * @param metaState The meta key modifier state.
+ * @return The associated character or combining accent, or 0 if none.
+ */
+ public int getUnicodeChar(int metaState) {
+ return getKeyCharacterMap().get(mKeyCode, metaState);
+ }
+
+ /**
+ * Get the character conversion data for a given key code.
+ *
+ * @param results A {@link KeyCharacterMap.KeyData} instance that will be
+ * filled with the results.
+ * @return True if the key was mapped. If the key was not mapped, results is not modified.
+ *
+ * @deprecated instead use {@link #getDisplayLabel()},
+ * {@link #getNumber()} or {@link #getUnicodeChar(int)}.
+ */
+ @Deprecated
+ public boolean getKeyData(KeyData results) {
+ return getKeyCharacterMap().getKeyData(mKeyCode, results);
+ }
+
+ /**
+ * Gets the first character in the character array that can be generated
+ * by the specified key code.
+ * <p>
+ * This is a convenience function that returns the same value as
+ * {@link #getMatch(char[],int) getMatch(chars, 0)}.
+ * </p>
+ *
+ * @param chars The array of matching characters to consider.
+ * @return The matching associated character, or 0 if none.
+ */
+ public char getMatch(char[] chars) {
+ return getMatch(chars, 0);
+ }
+
+ /**
+ * Gets the first character in the character array that can be generated
+ * by the specified key code. If there are multiple choices, prefers
+ * the one that would be generated with the specified meta key modifier state.
+ *
+ * @param chars The array of matching characters to consider.
+ * @param metaState The preferred meta key modifier state.
+ * @return The matching associated character, or 0 if none.
+ */
+ public char getMatch(char[] chars, int metaState) {
+ return getKeyCharacterMap().getMatch(mKeyCode, chars, metaState);
+ }
+
+ /**
+ * Gets the number or symbol associated with the key.
+ * <p>
+ * The character value is returned, not the numeric value.
+ * If the key is not a number, but is a symbol, the symbol is retuned.
+ * </p><p>
+ * This method is intended to to support dial pads and other numeric or
+ * symbolic entry on keyboards where certain keys serve dual function
+ * as alphabetic and symbolic keys. This method returns the number
+ * or symbol associated with the key independent of whether the user
+ * has pressed the required modifier.
+ * </p><p>
+ * For example, on one particular keyboard the keys on the top QWERTY row generate
+ * numbers when ALT is pressed such that ALT-Q maps to '1'. So for that keyboard
+ * when {@link #getNumber} is called with {@link KeyEvent#KEYCODE_Q} it returns '1'
+ * so that the user can type numbers without pressing ALT when it makes sense.
+ * </p>
+ *
+ * @return The associated numeric or symbolic character, or 0 if none.
+ */
+ public char getNumber() {
+ return getKeyCharacterMap().getNumber(mKeyCode);
+ }
+
+ /**
+ * Returns true if this key produces a glyph.
+ *
+ * @return True if the key is a printing key.
+ */
+ public boolean isPrintingKey() {
+ return getKeyCharacterMap().isPrintingKey(mKeyCode);
+ }
+
+ /**
+ * @deprecated Use {@link #dispatch(Callback, DispatcherState, Object)} instead.
+ */
+ @Deprecated
+ public final boolean dispatch(Callback receiver) {
+ return dispatch(receiver, null, null);
+ }
+
+ /**
+ * Deliver this key event to a {@link Callback} interface. If this is
+ * an ACTION_MULTIPLE event and it is not handled, then an attempt will
+ * be made to deliver a single normal event.
+ *
+ * @param receiver The Callback that will be given the event.
+ * @param state State information retained across events.
+ * @param target The target of the dispatch, for use in tracking.
+ *
+ * @return The return value from the Callback method that was called.
+ */
+ public final boolean dispatch(Callback receiver, DispatcherState state,
+ Object target) {
+ switch (mAction) {
+ case ACTION_DOWN: {
+ mFlags &= ~FLAG_START_TRACKING;
+ if (DEBUG) Log.v(TAG, "Key down to " + target + " in " + state
+ + ": " + this);
+ boolean res = receiver.onKeyDown(mKeyCode, this);
+ if (state != null) {
+ if (res && mRepeatCount == 0 && (mFlags&FLAG_START_TRACKING) != 0) {
+ if (DEBUG) Log.v(TAG, " Start tracking!");
+ state.startTracking(this, target);
+ } else if (isLongPress() && state.isTracking(this)) {
+ try {
+ if (receiver.onKeyLongPress(mKeyCode, this)) {
+ if (DEBUG) Log.v(TAG, " Clear from long press!");
+ state.performedLongPress(this);
+ res = true;
+ }
+ } catch (AbstractMethodError e) {
+ }
+ }
+ }
+ return res;
+ }
+ case ACTION_UP:
+ if (DEBUG) Log.v(TAG, "Key up to " + target + " in " + state
+ + ": " + this);
+ if (state != null) {
+ state.handleUpEvent(this);
+ }
+ return receiver.onKeyUp(mKeyCode, this);
+ case ACTION_MULTIPLE:
+ final int count = mRepeatCount;
+ final int code = mKeyCode;
+ if (receiver.onKeyMultiple(code, count, this)) {
+ return true;
+ }
+ if (code != KeyEvent.KEYCODE_UNKNOWN) {
+ mAction = ACTION_DOWN;
+ mRepeatCount = 0;
+ boolean handled = receiver.onKeyDown(code, this);
+ if (handled) {
+ mAction = ACTION_UP;
+ receiver.onKeyUp(code, this);
+ }
+ mAction = ACTION_MULTIPLE;
+ mRepeatCount = count;
+ return handled;
+ }
+ return false;
+ }
+ return false;
+ }
+
+ /**
+ * Use with {@link KeyEvent#dispatch(Callback, DispatcherState, Object)}
+ * for more advanced key dispatching, such as long presses.
+ */
+ public static class DispatcherState {
+ int mDownKeyCode;
+ Object mDownTarget;
+ SparseIntArray mActiveLongPresses = new SparseIntArray();
+
+ /**
+ * Reset back to initial state.
+ */
+ public void reset() {
+ if (DEBUG) Log.v(TAG, "Reset: " + this);
+ mDownKeyCode = 0;
+ mDownTarget = null;
+ mActiveLongPresses.clear();
+ }
+
+ /**
+ * Stop any tracking associated with this target.
+ */
+ public void reset(Object target) {
+ if (mDownTarget == target) {
+ if (DEBUG) Log.v(TAG, "Reset in " + target + ": " + this);
+ mDownKeyCode = 0;
+ mDownTarget = null;
+ }
+ }
+
+ /**
+ * Start tracking the key code associated with the given event. This
+ * can only be called on a key down. It will allow you to see any
+ * long press associated with the key, and will result in
+ * {@link KeyEvent#isTracking} return true on the long press and up
+ * events.
+ *
+ * <p>This is only needed if you are directly dispatching events, rather
+ * than handling them in {@link Callback#onKeyDown}.
+ */
+ public void startTracking(KeyEvent event, Object target) {
+ if (event.getAction() != ACTION_DOWN) {
+ throw new IllegalArgumentException(
+ "Can only start tracking on a down event");
+ }
+ if (DEBUG) Log.v(TAG, "Start trackingt in " + target + ": " + this);
+ mDownKeyCode = event.getKeyCode();
+ mDownTarget = target;
+ }
+
+ /**
+ * Return true if the key event is for a key code that is currently
+ * being tracked by the dispatcher.
+ */
+ public boolean isTracking(KeyEvent event) {
+ return mDownKeyCode == event.getKeyCode();
+ }
+
+ /**
+ * Keep track of the given event's key code as having performed an
+ * action with a long press, so no action should occur on the up.
+ * <p>This is only needed if you are directly dispatching events, rather
+ * than handling them in {@link Callback#onKeyLongPress}.
+ */
+ public void performedLongPress(KeyEvent event) {
+ mActiveLongPresses.put(event.getKeyCode(), 1);
+ }
+
+ /**
+ * Handle key up event to stop tracking. This resets the dispatcher state,
+ * and updates the key event state based on it.
+ * <p>This is only needed if you are directly dispatching events, rather
+ * than handling them in {@link Callback#onKeyUp}.
+ */
+ public void handleUpEvent(KeyEvent event) {
+ final int keyCode = event.getKeyCode();
+ if (DEBUG) Log.v(TAG, "Handle key up " + event + ": " + this);
+ int index = mActiveLongPresses.indexOfKey(keyCode);
+ if (index >= 0) {
+ if (DEBUG) Log.v(TAG, " Index: " + index);
+ event.mFlags |= FLAG_CANCELED | FLAG_CANCELED_LONG_PRESS;
+ mActiveLongPresses.removeAt(index);
+ }
+ if (mDownKeyCode == keyCode) {
+ if (DEBUG) Log.v(TAG, " Tracking!");
+ event.mFlags |= FLAG_TRACKING;
+ mDownKeyCode = 0;
+ mDownTarget = null;
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder msg = new StringBuilder();
+ msg.append("KeyEvent { action=").append(actionToString(mAction));
+ msg.append(", keyCode=").append(keyCodeToString(mKeyCode));
+ msg.append(", scanCode=").append(mScanCode);
+ if (mCharacters != null) {
+ msg.append(", characters=\"").append(mCharacters).append("\"");
+ }
+ msg.append(", metaState=").append(metaStateToString(mMetaState));
+ msg.append(", flags=0x").append(Integer.toHexString(mFlags));
+ msg.append(", repeatCount=").append(mRepeatCount);
+ msg.append(", eventTime=").append(mEventTime);
+ msg.append(", downTime=").append(mDownTime);
+ msg.append(", deviceId=").append(mDeviceId);
+ msg.append(", source=0x").append(Integer.toHexString(mSource));
+ msg.append(", displayId=").append(mDisplayId);
+ msg.append(" }");
+ return msg.toString();
+ }
+
+ /**
+ * Returns a string that represents the symbolic name of the specified action
+ * such as "ACTION_DOWN", or an equivalent numeric constant such as "35" if unknown.
+ *
+ * @param action The action.
+ * @return The symbolic name of the specified action.
+ * @hide
+ */
+ @TestApi
+ public static String actionToString(int action) {
+ switch (action) {
+ case ACTION_DOWN:
+ return "ACTION_DOWN";
+ case ACTION_UP:
+ return "ACTION_UP";
+ case ACTION_MULTIPLE:
+ return "ACTION_MULTIPLE";
+ default:
+ return Integer.toString(action);
+ }
+ }
+
+ /**
+ * Returns a string that represents the symbolic name of the specified keycode
+ * such as "KEYCODE_A", "KEYCODE_DPAD_UP", or an equivalent numeric constant
+ * such as "1001" if unknown.
+ *
+ * This function is intended to be used mostly for debugging, logging, and testing. It is not
+ * locale-specific and is not intended to be used in a user-facing manner.
+ *
+ * @param keyCode The key code.
+ * @return The symbolic name of the specified keycode.
+ *
+ * @see KeyCharacterMap#getDisplayLabel
+ */
+ public static String keyCodeToString(int keyCode) {
+ String symbolicName = nativeKeyCodeToString(keyCode);
+ return symbolicName != null ? LABEL_PREFIX + symbolicName : Integer.toString(keyCode);
+ }
+
+ /**
+ * Gets a keycode by its symbolic name such as "KEYCODE_A" or an equivalent
+ * numeric constant such as "29". For symbolic names,
+ * starting in {@link android.os.Build.VERSION_CODES#Q} the prefix "KEYCODE_" is optional.
+ *
+ * @param symbolicName The symbolic name of the keycode.
+ * @return The keycode or {@link #KEYCODE_UNKNOWN} if not found.
+ * @see #keyCodeToString(int)
+ */
+ public static int keyCodeFromString(@NonNull String symbolicName) {
+ try {
+ int keyCode = Integer.parseInt(symbolicName);
+ if (keyCodeIsValid(keyCode)) {
+ return keyCode;
+ }
+ } catch (NumberFormatException ex) {
+ }
+
+ if (symbolicName.startsWith(LABEL_PREFIX)) {
+ symbolicName = symbolicName.substring(LABEL_PREFIX.length());
+ }
+ int keyCode = nativeKeyCodeFromString(symbolicName);
+ if (keyCodeIsValid(keyCode)) {
+ return keyCode;
+ }
+ return KEYCODE_UNKNOWN;
+ }
+
+ private static boolean keyCodeIsValid(int keyCode) {
+ return keyCode >= KEYCODE_UNKNOWN && keyCode <= LAST_KEYCODE;
+ }
+
+ /**
+ * Returns a string that represents the symbolic name of the specified combined meta
+ * key modifier state flags such as "0", "META_SHIFT_ON",
+ * "META_ALT_ON|META_SHIFT_ON" or an equivalent numeric constant such as "0x10000000"
+ * if unknown.
+ *
+ * @param metaState The meta state.
+ * @return The symbolic name of the specified combined meta state flags.
+ * @hide
+ */
+ public static String metaStateToString(int metaState) {
+ if (metaState == 0) {
+ return "0";
+ }
+ StringBuilder result = null;
+ int i = 0;
+ while (metaState != 0) {
+ final boolean isSet = (metaState & 1) != 0;
+ metaState >>>= 1; // unsigned shift!
+ if (isSet) {
+ final String name = META_SYMBOLIC_NAMES[i];
+ if (result == null) {
+ if (metaState == 0) {
+ return name;
+ }
+ result = new StringBuilder(name);
+ } else {
+ result.append('|');
+ result.append(name);
+ }
+ }
+ i += 1;
+ }
+ return result.toString();
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<KeyEvent> CREATOR
+ = new Parcelable.Creator<KeyEvent>() {
+ @Override
+ public KeyEvent createFromParcel(Parcel in) {
+ in.readInt(); // skip token, we already know this is a KeyEvent
+ return KeyEvent.createFromParcelBody(in);
+ }
+
+ @Override
+ public KeyEvent[] newArray(int size) {
+ return new KeyEvent[size];
+ }
+ };
+
+ /** @hide */
+ public static KeyEvent createFromParcelBody(Parcel in) {
+ return new KeyEvent(in);
+ }
+
+ private KeyEvent(Parcel in) {
+ mId = in.readInt();
+ mDeviceId = in.readInt();
+ mSource = in.readInt();
+ mDisplayId = in.readInt();
+ mHmac = in.createByteArray();
+ mAction = in.readInt();
+ mKeyCode = in.readInt();
+ mRepeatCount = in.readInt();
+ mMetaState = in.readInt();
+ mScanCode = in.readInt();
+ mFlags = in.readInt();
+ mDownTime = in.readLong();
+ mEventTime = in.readLong();
+ mCharacters = in.readString();
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(PARCEL_TOKEN_KEY_EVENT);
+
+ out.writeInt(mId);
+ out.writeInt(mDeviceId);
+ out.writeInt(mSource);
+ out.writeInt(mDisplayId);
+ out.writeByteArray(mHmac);
+ out.writeInt(mAction);
+ out.writeInt(mKeyCode);
+ out.writeInt(mRepeatCount);
+ out.writeInt(mMetaState);
+ out.writeInt(mScanCode);
+ out.writeInt(mFlags);
+ out.writeLong(mDownTime);
+ out.writeLong(mEventTime);
+ out.writeString(mCharacters);
+ }
+}
diff --git a/services/core/java/com/android/server/StorageManagerService.java.orig b/services/core/java/com/android/server/StorageManagerService.java.orig
new file mode 100644
index 00000000000..498c52a5aa6
--- /dev/null
+++ b/services/core/java/com/android/server/StorageManagerService.java.orig
@@ -0,0 +1,4790 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static android.Manifest.permission.ACCESS_MTP;
+import static android.Manifest.permission.INSTALL_PACKAGES;
+import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
+import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
+import static android.Manifest.permission.WRITE_MEDIA_STORAGE;
+import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.OP_LEGACY_STORAGE;
+import static android.app.AppOpsManager.OP_MANAGE_EXTERNAL_STORAGE;
+import static android.app.AppOpsManager.OP_READ_EXTERNAL_STORAGE;
+import static android.app.AppOpsManager.OP_REQUEST_INSTALL_PACKAGES;
+import static android.app.AppOpsManager.OP_WRITE_EXTERNAL_STORAGE;
+import static android.content.pm.PackageManager.MATCH_ANY_USER;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
+import static android.os.storage.OnObbStateChangeListener.ERROR_ALREADY_MOUNTED;
+import static android.os.storage.OnObbStateChangeListener.ERROR_COULD_NOT_MOUNT;
+import static android.os.storage.OnObbStateChangeListener.ERROR_COULD_NOT_UNMOUNT;
+import static android.os.storage.OnObbStateChangeListener.ERROR_INTERNAL;
+import static android.os.storage.OnObbStateChangeListener.ERROR_NOT_MOUNTED;
+import static android.os.storage.OnObbStateChangeListener.ERROR_PERMISSION_DENIED;
+import static android.os.storage.OnObbStateChangeListener.MOUNTED;
+import static android.os.storage.OnObbStateChangeListener.UNMOUNTED;
+import static android.os.storage.StorageManager.PROP_FORCED_SCOPED_STORAGE_WHITELIST;
+import static android.os.storage.StorageManager.PROP_FUSE;
+import static android.os.storage.StorageManager.PROP_SETTINGS_FUSE;
+
+import static com.android.internal.util.XmlUtils.readIntAttribute;
+import static com.android.internal.util.XmlUtils.readLongAttribute;
+import static com.android.internal.util.XmlUtils.readStringAttribute;
+import static com.android.internal.util.XmlUtils.writeIntAttribute;
+import static com.android.internal.util.XmlUtils.writeLongAttribute;
+import static com.android.internal.util.XmlUtils.writeStringAttribute;
+
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
+
+import android.Manifest;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.AppOpsManager;
+import android.app.IActivityManager;
+import android.app.KeyguardManager;
+import android.app.admin.SecurityLog;
+import android.app.usage.StorageStatsManager;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.IPackageMoveObserver;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.ProviderInfo;
+import android.content.pm.UserInfo;
+import android.content.res.Configuration;
+import android.content.res.ObbInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.DropBoxManager;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.IStoraged;
+import android.os.IVold;
+import android.os.IVoldListener;
+import android.os.IVoldMountCallback;
+import android.os.IVoldTaskListener;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.ParcelableException;
+import android.os.PersistableBundle;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.UserManagerInternal;
+import android.os.storage.DiskInfo;
+import android.os.storage.IObbActionListener;
+import android.os.storage.IStorageEventListener;
+import android.os.storage.IStorageManager;
+import android.os.storage.IStorageShutdownObserver;
+import android.os.storage.OnObbStateChangeListener;
+import android.os.storage.StorageManager;
+import android.os.storage.StorageManagerInternal;
+import android.os.storage.StorageVolume;
+import android.os.storage.VolumeInfo;
+import android.os.storage.VolumeRecord;
+import android.provider.DeviceConfig;
+import android.provider.DocumentsContract;
+import android.provider.Downloads;
+import android.provider.MediaStore;
+import android.provider.Settings;
+import android.sysprop.VoldProperties;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.AtomicFile;
+import android.util.DataUnit;
+import android.util.FeatureFlagUtils;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Slog;
+import android.util.TimeUtils;
+import android.util.Xml;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.app.IAppOpsService;
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.os.AppFuseMount;
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.os.FuseUnavailableMountException;
+import com.android.internal.os.SomeArgs;
+import com.android.internal.os.Zygote;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
+import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.HexDump;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.server.pm.Installer;
+import com.android.server.storage.AppFuseBridge;
+import com.android.server.storage.StorageSessionController;
+import com.android.server.storage.StorageSessionController.ExternalStorageServiceException;
+import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.server.wm.ActivityTaskManagerInternal.ScreenObserver;
+
+import libcore.io.IoUtils;
+import libcore.util.EmptyArray;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.spec.KeySpec;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+
+/**
+ * Service responsible for various storage media. Connects to {@code vold} to
+ * watch for and manage dynamically added storage, such as SD cards and USB mass
+ * storage. Also decides how storage should be presented to users on the device.
+ */
+class StorageManagerService extends IStorageManager.Stub
+ implements Watchdog.Monitor, ScreenObserver {
+
+ // Static direct instance pointer for the tightly-coupled idle service to use
+ static StorageManagerService sSelf = null;
+
+ /* Read during boot to decide whether to enable zram when available */
+ private static final String ZRAM_ENABLED_PROPERTY =
+ "persist.sys.zram_enabled";
+
+ private static final boolean ENABLE_ISOLATED_STORAGE = StorageManager.hasIsolatedStorage();
+
+ // A system property to control if obb app data isolation is enabled in vold.
+ private static final String ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY =
+ "persist.sys.vold_app_data_isolation_enabled";
+
+ // How long we wait to reset storage, if we failed to call onMount on the
+ // external storage service.
+ public static final int FAILED_MOUNT_RESET_TIMEOUT_SECONDS = 10;
+ /**
+ * If {@code 1}, enables the isolated storage feature. If {@code -1},
+ * disables the isolated storage feature. If {@code 0}, uses the default
+ * value from the build system.
+ */
+ private static final String ISOLATED_STORAGE_ENABLED = "isolated_storage_enabled";
+
+ /**
+ * If {@code 1}, enables FuseDaemon to intercept file system ops. If {@code -1},
+ * disables FuseDaemon. If {@code 0}, uses the default value from the build system.
+ */
+ private static final String FUSE_ENABLED = "fuse_enabled";
+ private static final boolean DEFAULT_FUSE_ENABLED = true;
+
+ @GuardedBy("mLock")
+ private final Set<Integer> mFuseMountedUser = new ArraySet<>();
+
+ public static class Lifecycle extends SystemService {
+ private StorageManagerService mStorageManagerService;
+
+ public Lifecycle(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ mStorageManagerService = new StorageManagerService(getContext());
+ publishBinderService("mount", mStorageManagerService);
+ mStorageManagerService.start();
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+ mStorageManagerService.servicesReady();
+ } else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+ mStorageManagerService.systemReady();
+ } else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
+ mStorageManagerService.bootCompleted();
+ }
+ }
+
+ @Override
+ public void onSwitchUser(int userHandle) {
+ mStorageManagerService.mCurrentUserId = userHandle;
+ }
+
+ @Override
+ public void onUnlockUser(int userHandle) {
+ mStorageManagerService.onUnlockUser(userHandle);
+ }
+
+ @Override
+ public void onCleanupUser(int userHandle) {
+ mStorageManagerService.onCleanupUser(userHandle);
+ }
+
+ @Override
+ public void onStopUser(int userHandle) {
+ mStorageManagerService.onStopUser(userHandle);
+ }
+
+ @Override
+ public void onUserStarting(TargetUser user) {
+ mStorageManagerService.snapshotAndMonitorLegacyStorageAppOp(user.getUserHandle());
+ }
+ }
+
+ private static final boolean DEBUG_EVENTS = false;
+ private static final boolean DEBUG_OBB = false;
+
+ /**
+ * We now talk to vold over Binder, and it has its own internal lock to
+ * serialize certain calls. All long-running operations have been migrated
+ * to be async with callbacks, so we want watchdog to fire if vold wedges.
+ */
+ private static final boolean WATCHDOG_ENABLE = true;
+
+ /**
+ * Our goal is for all Android devices to be usable as development devices,
+ * which includes the new Direct Boot mode added in N. For devices that
+ * don't have native FBE support, we offer an emulation mode for developer
+ * testing purposes, but if it's prohibitively difficult to support this
+ * mode, it can be disabled for specific products using this flag.
+ */
+ private static final boolean EMULATE_FBE_SUPPORTED = true;
+
+ private static final String TAG = "StorageManagerService";
+ private static final boolean LOCAL_LOGV = Log.isLoggable(TAG, Log.VERBOSE);
+
+ private static final String TAG_STORAGE_BENCHMARK = "storage_benchmark";
+ private static final String TAG_STORAGE_TRIM = "storage_trim";
+
+ /** Magic value sent by MoveTask.cpp */
+ private static final int MOVE_STATUS_COPY_FINISHED = 82;
+
+ private static final int VERSION_INIT = 1;
+ private static final int VERSION_ADD_PRIMARY = 2;
+ private static final int VERSION_FIX_PRIMARY = 3;
+
+ private static final String TAG_VOLUMES = "volumes";
+ private static final String ATTR_VERSION = "version";
+ private static final String ATTR_PRIMARY_STORAGE_UUID = "primaryStorageUuid";
+ private static final String TAG_VOLUME = "volume";
+ private static final String ATTR_TYPE = "type";
+ private static final String ATTR_FS_UUID = "fsUuid";
+ private static final String ATTR_PART_GUID = "partGuid";
+ private static final String ATTR_NICKNAME = "nickname";
+ private static final String ATTR_USER_FLAGS = "userFlags";
+ private static final String ATTR_CREATED_MILLIS = "createdMillis";
+ private static final String ATTR_LAST_SEEN_MILLIS = "lastSeenMillis";
+ private static final String ATTR_LAST_TRIM_MILLIS = "lastTrimMillis";
+ private static final String ATTR_LAST_BENCH_MILLIS = "lastBenchMillis";
+
+ private static final String[] ALL_STORAGE_PERMISSIONS = {
+ Manifest.permission.READ_EXTERNAL_STORAGE,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE
+ };
+
+ @Nullable public static String sMediaStoreAuthorityProcessName;
+
+ private final AtomicFile mSettingsFile;
+
+ /**
+ * <em>Never</em> hold the lock while performing downcalls into vold, since
+ * unsolicited events can suddenly appear to update data structures.
+ */
+ private final Object mLock = LockGuard.installNewLock(LockGuard.INDEX_STORAGE);
+
+ /**
+ * Similar to {@link #mLock}, never hold this lock while performing downcalls into vold.
+ * Also, never hold this while calling into PackageManagerService since it is used in callbacks
+ * from PackageManagerService.
+ *
+ * If both {@link #mLock} and this lock need to be held, {@link #mLock} should be acquired
+ * before this.
+ *
+ * Use -PL suffix for methods that need to called with this lock held.
+ */
+ private final Object mPackagesLock = new Object();
+
+ /**
+ * mLocalUnlockedUsers affects the return value of isUserUnlocked. If
+ * any value in the array changes, then the binder cache for
+ * isUserUnlocked must be invalidated. When adding mutating methods to
+ * WatchedLockedUsers, be sure to invalidate the cache in the new
+ * methods.
+ */
+ private class WatchedLockedUsers {
+ private int[] users = EmptyArray.INT;
+ public WatchedLockedUsers() {
+ invalidateIsUserUnlockedCache();
+ }
+ public void append(int userId) {
+ users = ArrayUtils.appendInt(users, userId);
+ invalidateIsUserUnlockedCache();
+ }
+ public void remove(int userId) {
+ users = ArrayUtils.removeInt(users, userId);
+ invalidateIsUserUnlockedCache();
+ }
+ public boolean contains(int userId) {
+ return ArrayUtils.contains(users, userId);
+ }
+ public int[] all() {
+ return users;
+ }
+ @Override
+ public String toString() {
+ return Arrays.toString(users);
+ }
+ private void invalidateIsUserUnlockedCache() {
+ UserManager.invalidateIsUserUnlockedCache();
+ }
+ }
+
+ /** Set of users that we know are unlocked. */
+ @GuardedBy("mLock")
+ private WatchedLockedUsers mLocalUnlockedUsers = new WatchedLockedUsers();
+ /** Set of users that system knows are unlocked. */
+ @GuardedBy("mLock")
+ private int[] mSystemUnlockedUsers = EmptyArray.INT;
+
+ /** Map from disk ID to disk */
+ @GuardedBy("mLock")
+ private ArrayMap<String, DiskInfo> mDisks = new ArrayMap<>();
+ /** Map from volume ID to disk */
+ @GuardedBy("mLock")
+ private final ArrayMap<String, VolumeInfo> mVolumes = new ArrayMap<>();
+
+ /** Map from UUID to record */
+ @GuardedBy("mLock")
+ private ArrayMap<String, VolumeRecord> mRecords = new ArrayMap<>();
+ @GuardedBy("mLock")
+ private String mPrimaryStorageUuid;
+
+ /** Map from disk ID to latches */
+ @GuardedBy("mLock")
+ private ArrayMap<String, CountDownLatch> mDiskScanLatches = new ArrayMap<>();
+
+ @GuardedBy("mLock")
+ private IPackageMoveObserver mMoveCallback;
+ @GuardedBy("mLock")
+ private String mMoveTargetUuid;
+
+ private volatile int mMediaStoreAuthorityAppId = -1;
+
+ private volatile int mDownloadsAuthorityAppId = -1;
+
+ private volatile int mExternalStorageAuthorityAppId = -1;
+
+ private volatile int mCurrentUserId = UserHandle.USER_SYSTEM;
+
+ private final Installer mInstaller;
+
+ /** Holding lock for AppFuse business */
+ private final Object mAppFuseLock = new Object();
+
+ @GuardedBy("mAppFuseLock")
+ private int mNextAppFuseName = 0;
+
+ @GuardedBy("mAppFuseLock")
+ private AppFuseBridge mAppFuseBridge = null;
+
+ /** Matches known application dir paths. The first group contains the generic part of the path,
+ * the second group contains the user id (or null if it's a public volume without users), the
+ * third group contains the package name, and the fourth group the remainder of the path.
+ */
+ public static final Pattern KNOWN_APP_DIR_PATHS = Pattern.compile(
+ "(?i)(^/storage/[^/]+/(?:([0-9]+)/)?Android/(?:data|media|obb|sandbox)/)([^/]+)(/.*)?");
+
+
+ /** Automotive device unlockes users before system boot complete and this requires special
+ * handling as vold reset can lead into race conditions. When this is set, all users unlocked
+ * in {@code UserManager} level are unlocked after vold reset.
+ */
+ private final boolean mIsAutomotive;
+
+ private VolumeInfo findVolumeByIdOrThrow(String id) {
+ synchronized (mLock) {
+ final VolumeInfo vol = mVolumes.get(id);
+ if (vol != null) {
+ return vol;
+ }
+ }
+ throw new IllegalArgumentException("No volume found for ID " + id);
+ }
+
+ private String findVolumeIdForPathOrThrow(String path) {
+ synchronized (mLock) {
+ for (int i = 0; i < mVolumes.size(); i++) {
+ final VolumeInfo vol = mVolumes.valueAt(i);
+ if (vol.path != null && path.startsWith(vol.path)) {
+ return vol.id;
+ }
+ }
+ }
+ throw new IllegalArgumentException("No volume found for path " + path);
+ }
+
+ private VolumeRecord findRecordForPath(String path) {
+ synchronized (mLock) {
+ for (int i = 0; i < mVolumes.size(); i++) {
+ final VolumeInfo vol = mVolumes.valueAt(i);
+ if (vol.path != null && path.startsWith(vol.path)) {
+ return mRecords.get(vol.fsUuid);
+ }
+ }
+ }
+ return null;
+ }
+
+ private String scrubPath(String path) {
+ if (path.startsWith(Environment.getDataDirectory().getAbsolutePath())) {
+ return "internal";
+ }
+ final VolumeRecord rec = findRecordForPath(path);
+ if (rec == null || rec.createdMillis == 0) {
+ return "unknown";
+ } else {
+ return "ext:" + (int) ((System.currentTimeMillis() - rec.createdMillis)
+ / DateUtils.WEEK_IN_MILLIS) + "w";
+ }
+ }
+
+ private @Nullable VolumeInfo findStorageForUuid(String volumeUuid) {
+ final StorageManager storage = mContext.getSystemService(StorageManager.class);
+ if (Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, volumeUuid)) {
+ return storage.findVolumeById(VolumeInfo.ID_EMULATED_INTERNAL + ";" + 0);
+ } else if (Objects.equals(StorageManager.UUID_PRIMARY_PHYSICAL, volumeUuid)) {
+ return storage.getPrimaryPhysicalVolume();
+ } else {
+ return storage.findEmulatedForPrivate(storage.findVolumeByUuid(volumeUuid));
+ }
+ }
+
+ private boolean shouldBenchmark() {
+ final long benchInterval = Settings.Global.getLong(mContext.getContentResolver(),
+ Settings.Global.STORAGE_BENCHMARK_INTERVAL, DateUtils.WEEK_IN_MILLIS);
+ if (benchInterval == -1) {
+ return false;
+ } else if (benchInterval == 0) {
+ return true;
+ }
+
+ synchronized (mLock) {
+ for (int i = 0; i < mVolumes.size(); i++) {
+ final VolumeInfo vol = mVolumes.valueAt(i);
+ final VolumeRecord rec = mRecords.get(vol.fsUuid);
+ if (vol.isMountedWritable() && rec != null) {
+ final long benchAge = System.currentTimeMillis() - rec.lastBenchMillis;
+ if (benchAge >= benchInterval) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ }
+
+ private CountDownLatch findOrCreateDiskScanLatch(String diskId) {
+ synchronized (mLock) {
+ CountDownLatch latch = mDiskScanLatches.get(diskId);
+ if (latch == null) {
+ latch = new CountDownLatch(1);
+ mDiskScanLatches.put(diskId, latch);
+ }
+ return latch;
+ }
+ }
+
+ /** List of crypto types.
+ * These must match CRYPT_TYPE_XXX in cryptfs.h AND their
+ * corresponding commands in CommandListener.cpp */
+ public static final String[] CRYPTO_TYPES
+ = { "password", "default", "pattern", "pin" };
+
+ private final Context mContext;
+ private final ContentResolver mResolver;
+
+ private volatile IVold mVold;
+ private volatile IStoraged mStoraged;
+
+ private volatile boolean mBootCompleted = false;
+ private volatile boolean mDaemonConnected = false;
+ private volatile boolean mSecureKeyguardShowing = true;
+
+ private PackageManagerInternal mPmInternal;
+
+ private IPackageManager mIPackageManager;
+ private IAppOpsService mIAppOpsService;
+
+ private final Callbacks mCallbacks;
+ private final LockPatternUtils mLockPatternUtils;
+
+ /**
+ * The size of the crypto algorithm key in bits for OBB files. Currently
+ * Twofish is used which takes 128-bit keys.
+ */
+ private static final int CRYPTO_ALGORITHM_KEY_SIZE = 128;
+
+ /**
+ * The number of times to run SHA1 in the PBKDF2 function for OBB files.
+ * 1024 is reasonably secure and not too slow.
+ */
+ private static final int PBKDF2_HASH_ROUNDS = 1024;
+
+ /**
+ * Mounted OBB tracking information. Used to track the current state of all
+ * OBBs.
+ */
+ final private Map<IBinder, List<ObbState>> mObbMounts = new HashMap<IBinder, List<ObbState>>();
+
+ /** Map from raw paths to {@link ObbState}. */
+ final private Map<String, ObbState> mObbPathToStateMap = new HashMap<String, ObbState>();
+
+ // Not guarded by a lock.
+ private final StorageManagerInternalImpl mStorageManagerInternal
+ = new StorageManagerInternalImpl();
+
+ // Not guarded by a lock.
+ private final StorageSessionController mStorageSessionController;
+
+ private final boolean mIsFuseEnabled;
+
+ private final boolean mVoldAppDataIsolationEnabled;
+
+ @GuardedBy("mLock")
+ private final Set<Integer> mUidsWithLegacyExternalStorage = new ArraySet<>();
+ // Not guarded by lock, always used on the ActivityManager thread
+ private final Map<Integer, PackageMonitor> mPackageMonitorsForUser = new ArrayMap<>();
+
+
+ class ObbState implements IBinder.DeathRecipient {
+ public ObbState(String rawPath, String canonicalPath, int callingUid,
+ IObbActionListener token, int nonce, String volId) {
+ this.rawPath = rawPath;
+ this.canonicalPath = canonicalPath;
+ this.ownerGid = UserHandle.getSharedAppGid(callingUid);
+ this.token = token;
+ this.nonce = nonce;
+ this.volId = volId;
+ }
+
+ final String rawPath;
+ final String canonicalPath;
+
+ final int ownerGid;
+
+ // Token of remote Binder caller
+ final IObbActionListener token;
+
+ // Identifier to pass back to the token
+ final int nonce;
+
+ String volId;
+
+ public IBinder getBinder() {
+ return token.asBinder();
+ }
+
+ @Override
+ public void binderDied() {
+ ObbAction action = new UnmountObbAction(this, true);
+ mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
+ }
+
+ public void link() throws RemoteException {
+ getBinder().linkToDeath(this, 0);
+ }
+
+ public void unlink() {
+ getBinder().unlinkToDeath(this, 0);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("ObbState{");
+ sb.append("rawPath=").append(rawPath);
+ sb.append(",canonicalPath=").append(canonicalPath);
+ sb.append(",ownerGid=").append(ownerGid);
+ sb.append(",token=").append(token);
+ sb.append(",binder=").append(getBinder());
+ sb.append(",volId=").append(volId);
+ sb.append('}');
+ return sb.toString();
+ }
+ }
+
+ // OBB Action Handler
+ final private ObbActionHandler mObbActionHandler;
+
+ // OBB action handler messages
+ private static final int OBB_RUN_ACTION = 1;
+ private static final int OBB_FLUSH_MOUNT_STATE = 2;
+
+ // Last fstrim operation tracking
+ private static final String LAST_FSTRIM_FILE = "last-fstrim";
+ private final File mLastMaintenanceFile;
+ private long mLastMaintenance;
+
+ // Handler messages
+ private static final int H_SYSTEM_READY = 1;
+ private static final int H_DAEMON_CONNECTED = 2;
+ private static final int H_SHUTDOWN = 3;
+ private static final int H_FSTRIM = 4;
+ private static final int H_VOLUME_MOUNT = 5;
+ private static final int H_VOLUME_BROADCAST = 6;
+ private static final int H_INTERNAL_BROADCAST = 7;
+ private static final int H_VOLUME_UNMOUNT = 8;
+ private static final int H_PARTITION_FORGET = 9;
+ private static final int H_RESET = 10;
+ private static final int H_RUN_IDLE_MAINT = 11;
+ private static final int H_ABORT_IDLE_MAINT = 12;
+ private static final int H_BOOT_COMPLETED = 13;
+ private static final int H_COMPLETE_UNLOCK_USER = 14;
+ private static final int H_VOLUME_STATE_CHANGED = 15;
+
+ class StorageManagerServiceHandler extends Handler {
+ public StorageManagerServiceHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case H_SYSTEM_READY: {
+ handleSystemReady();
+ break;
+ }
+ case H_BOOT_COMPLETED: {
+ handleBootCompleted();
+ break;
+ }
+ case H_DAEMON_CONNECTED: {
+ handleDaemonConnected();
+ break;
+ }
+ case H_FSTRIM: {
+ Slog.i(TAG, "Running fstrim idle maintenance");
+
+ // Remember when we kicked it off
+ try {
+ mLastMaintenance = System.currentTimeMillis();
+ mLastMaintenanceFile.setLastModified(mLastMaintenance);
+ } catch (Exception e) {
+ Slog.e(TAG, "Unable to record last fstrim!");
+ }
+
+ // TODO: Reintroduce shouldBenchmark() test
+ fstrim(0, null);
+
+ // invoke the completion callback, if any
+ // TODO: fstrim is non-blocking, so remove this useless callback
+ Runnable callback = (Runnable) msg.obj;
+ if (callback != null) {
+ callback.run();
+ }
+ break;
+ }
+ case H_SHUTDOWN: {
+ final IStorageShutdownObserver obs = (IStorageShutdownObserver) msg.obj;
+ boolean success = false;
+ try {
+ mVold.shutdown();
+ success = true;
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+ if (obs != null) {
+ try {
+ obs.onShutDownComplete(success ? 0 : -1);
+ } catch (Exception ignored) {
+ }
+ }
+ break;
+ }
+ case H_VOLUME_MOUNT: {
+ final VolumeInfo vol = (VolumeInfo) msg.obj;
+ if (isMountDisallowed(vol)) {
+ Slog.i(TAG, "Ignoring mount " + vol.getId() + " due to policy");
+ break;
+ }
+
+ mount(vol);
+ break;
+ }
+ case H_VOLUME_UNMOUNT: {
+ final VolumeInfo vol = (VolumeInfo) msg.obj;
+ unmount(vol);
+ break;
+ }
+ case H_VOLUME_BROADCAST: {
+ final StorageVolume userVol = (StorageVolume) msg.obj;
+ final String envState = userVol.getState();
+ Slog.d(TAG, "Volume " + userVol.getId() + " broadcasting " + envState + " to "
+ + userVol.getOwner());
+
+ final String action = VolumeInfo.getBroadcastForEnvironment(envState);
+ if (action != null) {
+ final Intent intent = new Intent(action,
+ Uri.fromFile(userVol.getPathFile()));
+ intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, userVol);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+ mContext.sendBroadcastAsUser(intent, userVol.getOwner());
+ }
+ break;
+ }
+ case H_INTERNAL_BROADCAST: {
+ // Internal broadcasts aimed at system components, not for
+ // third-party apps.
+ final Intent intent = (Intent) msg.obj;
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
+ android.Manifest.permission.WRITE_MEDIA_STORAGE);
+ break;
+ }
+ case H_PARTITION_FORGET: {
+ final VolumeRecord rec = (VolumeRecord) msg.obj;
+ forgetPartition(rec.partGuid, rec.fsUuid);
+ break;
+ }
+ case H_RESET: {
+ resetIfBootedAndConnected();
+ break;
+ }
+ case H_RUN_IDLE_MAINT: {
+ Slog.i(TAG, "Running idle maintenance");
+ runIdleMaint((Runnable)msg.obj);
+ break;
+ }
+ case H_ABORT_IDLE_MAINT: {
+ Slog.i(TAG, "Aborting idle maintenance");
+ abortIdleMaint((Runnable)msg.obj);
+ break;
+ }
+ case H_COMPLETE_UNLOCK_USER: {
+ completeUnlockUser((int) msg.obj);
+ break;
+ }
+ case H_VOLUME_STATE_CHANGED: {
+ final SomeArgs args = (SomeArgs) msg.obj;
+ onVolumeStateChangedAsync((VolumeInfo) args.arg1, (int) args.arg2,
+ (int) args.arg3);
+ }
+ }
+ }
+ }
+
+ private final Handler mHandler;
+
+ private BroadcastReceiver mUserReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+ Preconditions.checkArgument(userId >= 0);
+
+ try {
+ if (Intent.ACTION_USER_ADDED.equals(action)) {
+ final UserManager um = mContext.getSystemService(UserManager.class);
+ final int userSerialNumber = um.getUserSerialNumber(userId);
+ mVold.onUserAdded(userId, userSerialNumber);
+ } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
+ synchronized (mVolumes) {
+ final int size = mVolumes.size();
+ for (int i = 0; i < size; i++) {
+ final VolumeInfo vol = mVolumes.valueAt(i);
+ if (vol.mountUserId == userId) {
+ vol.mountUserId = UserHandle.USER_NULL;
+ mHandler.obtainMessage(H_VOLUME_UNMOUNT, vol).sendToTarget();
+ }
+ }
+ }
+ mVold.onUserRemoved(userId);
+ }
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+ }
+ };
+
+ private void waitForLatch(CountDownLatch latch, String condition, long timeoutMillis)
+ throws TimeoutException {
+ final long startMillis = SystemClock.elapsedRealtime();
+ while (true) {
+ try {
+ if (latch.await(5000, TimeUnit.MILLISECONDS)) {
+ return;
+ } else {
+ Slog.w(TAG, "Thread " + Thread.currentThread().getName()
+ + " still waiting for " + condition + "...");
+ }
+ } catch (InterruptedException e) {
+ Slog.w(TAG, "Interrupt while waiting for " + condition);
+ }
+ if (timeoutMillis > 0 && SystemClock.elapsedRealtime() > startMillis + timeoutMillis) {
+ throw new TimeoutException("Thread " + Thread.currentThread().getName()
+ + " gave up waiting for " + condition + " after " + timeoutMillis + "ms");
+ }
+ }
+ }
+
+ private void handleSystemReady() {
+ // Start scheduling nominally-daily fstrim operations
+ MountServiceIdler.scheduleIdlePass(mContext);
+
+ // Toggle zram-enable system property in response to settings
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.ZRAM_ENABLED),
+ false /*notifyForDescendants*/,
+ new ContentObserver(null /* current thread */) {
+ @Override
+ public void onChange(boolean selfChange) {
+ refreshZramSettings();
+ }
+ });
+ refreshZramSettings();
+
+ // Schedule zram writeback unless zram is disabled by persist.sys.zram_enabled
+ String zramPropValue = SystemProperties.get(ZRAM_ENABLED_PROPERTY);
+ if (!zramPropValue.equals("0")
+ && mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_zramWriteback)) {
+ ZramWriteback.scheduleZramWriteback(mContext);
+ }
+ // Toggle isolated-enable system property in response to settings
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.ISOLATED_STORAGE_REMOTE),
+ false /*notifyForDescendants*/,
+ new ContentObserver(null /* current thread */) {
+ @Override
+ public void onChange(boolean selfChange) {
+ refreshIsolatedStorageSettings();
+ }
+ });
+ // For now, simply clone property when it changes
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+ mContext.getMainExecutor(), (properties) -> {
+ refreshIsolatedStorageSettings();
+ refreshFuseSettings();
+ });
+ refreshIsolatedStorageSettings();
+ }
+
+ /**
+ * Update the zram_enabled system property (which init reads to
+ * decide whether to enable zram) to reflect the zram_enabled
+ * preference (which we can change for experimentation purposes).
+ */
+ private void refreshZramSettings() {
+ String propertyValue = SystemProperties.get(ZRAM_ENABLED_PROPERTY);
+ if ("".equals(propertyValue)) {
+ return; // System doesn't have zram toggling support
+ }
+ String desiredPropertyValue =
+ Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.ZRAM_ENABLED,
+ 1) != 0
+ ? "1" : "0";
+ if (!desiredPropertyValue.equals(propertyValue)) {
+ // Avoid redundant disk writes by setting only if we're
+ // changing the property value. There's no race: we're the
+ // sole writer.
+ SystemProperties.set(ZRAM_ENABLED_PROPERTY, desiredPropertyValue);
+ // Schedule writeback only if zram is being enabled.
+ if (desiredPropertyValue.equals("1")
+ && mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_zramWriteback)) {
+ ZramWriteback.scheduleZramWriteback(mContext);
+ }
+ }
+ }
+
+ private void refreshIsolatedStorageSettings() {
+ // Always copy value from newer DeviceConfig location
+ Settings.Global.putString(mResolver,
+ Settings.Global.ISOLATED_STORAGE_REMOTE,
+ DeviceConfig.getProperty(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+ ISOLATED_STORAGE_ENABLED));
+
+ final int local = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.ISOLATED_STORAGE_LOCAL, 0);
+ final int remote = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.ISOLATED_STORAGE_REMOTE, 0);
+
+ // Walk down precedence chain; we prefer local settings first, then
+ // remote settings, before finally falling back to hard-coded default.
+ final boolean res;
+ if (local == -1) {
+ res = false;
+ } else if (local == 1) {
+ res = true;
+ } else if (remote == -1) {
+ res = false;
+ } else if (remote == 1) {
+ res = true;
+ } else {
+ res = true;
+ }
+
+ Slog.d(TAG, "Isolated storage local flag " + local + " and remote flag "
+ + remote + " resolved to " + res);
+ SystemProperties.set(StorageManager.PROP_ISOLATED_STORAGE, Boolean.toString(res));
+ }
+
+ /**
+ * The most recent flag change takes precedence. Change fuse Settings flag if Device Config is
+ * changed. Settings flag change will in turn change fuse system property (persist.sys.fuse)
+ * whenever the user reboots.
+ */
+ private void refreshFuseSettings() {
+ int isFuseEnabled = DeviceConfig.getInt(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+ FUSE_ENABLED, 0);
+ if (isFuseEnabled == 1) {
+ Slog.d(TAG, "Device Config flag for FUSE is enabled, turn Settings fuse flag on");
+ SystemProperties.set(FeatureFlagUtils.PERSIST_PREFIX
+ + FeatureFlagUtils.SETTINGS_FUSE_FLAG, "true");
+ } else if (isFuseEnabled == -1) {
+ Slog.d(TAG, "Device Config flag for FUSE is disabled, turn Settings fuse flag off");
+ SystemProperties.set(FeatureFlagUtils.PERSIST_PREFIX
+ + FeatureFlagUtils.SETTINGS_FUSE_FLAG, "false");
+ }
+ // else, keep the build config.
+ // This can be overridden by direct adjustment of persist.sys.fflag.override.settings_fuse
+ }
+
+ /**
+ * MediaProvider has a ton of code that makes assumptions about storage
+ * paths never changing, so we outright kill them to pick up new state.
+ */
+ @Deprecated
+ private void killMediaProvider(List<UserInfo> users) {
+ if (users == null) return;
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ for (UserInfo user : users) {
+ // System user does not have media provider, so skip.
+ if (user.isSystemOnly()) continue;
+
+ final ProviderInfo provider = mPmInternal.resolveContentProvider(
+ MediaStore.AUTHORITY, PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+ user.id);
+ if (provider != null) {
+ final IActivityManager am = ActivityManager.getService();
+ try {
+ am.killApplication(provider.applicationInfo.packageName,
+ UserHandle.getAppId(provider.applicationInfo.uid),
+ UserHandle.USER_ALL, "vold reset");
+ // We only need to run this once. It will kill all users' media processes.
+ break;
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void addInternalVolumeLocked() {
+ // Create a stub volume that represents internal storage
+ final VolumeInfo internal = new VolumeInfo(VolumeInfo.ID_PRIVATE_INTERNAL,
+ VolumeInfo.TYPE_PRIVATE, null, null);
+ internal.state = VolumeInfo.STATE_MOUNTED;
+ internal.path = Environment.getDataDirectory().getAbsolutePath();
+ mVolumes.put(internal.id, internal);
+ }
+
+ private void initIfBootedAndConnected() {
+ Slog.d(TAG, "Thinking about init, mBootCompleted=" + mBootCompleted
+ + ", mDaemonConnected=" + mDaemonConnected);
+ if (mBootCompleted && mDaemonConnected
+ && !StorageManager.isFileEncryptedNativeOnly()) {
+ // When booting a device without native support, make sure that our
+ // user directories are locked or unlocked based on the current
+ // emulation status.
+ final boolean initLocked = StorageManager.isFileEncryptedEmulatedOnly();
+ Slog.d(TAG, "Setting up emulation state, initlocked=" + initLocked);
+ final List<UserInfo> users = mContext.getSystemService(UserManager.class).getUsers();
+ for (UserInfo user : users) {
+ try {
+ if (initLocked) {
+ mVold.lockUserKey(user.id);
+ } else {
+ mVold.unlockUserKey(user.id, user.serialNumber, encodeBytes(null),
+ encodeBytes(null));
+ }
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+ }
+ }
+ }
+
+ private void resetIfBootedAndConnected() {
+ Slog.d(TAG, "Thinking about reset, mBootCompleted=" + mBootCompleted
+ + ", mDaemonConnected=" + mDaemonConnected);
+ if (mBootCompleted && mDaemonConnected) {
+ final UserManager userManager = mContext.getSystemService(UserManager.class);
+ final List<UserInfo> users = userManager.getUsers();
+
+ if (mIsFuseEnabled) {
+ mStorageSessionController.onReset(mVold, () -> {
+ mHandler.removeCallbacksAndMessages(null);
+ });
+ } else {
+ killMediaProvider(users);
+ }
+
+ final int[] systemUnlockedUsers;
+ synchronized (mLock) {
+ // make copy as sorting can change order
+ systemUnlockedUsers = Arrays.copyOf(mSystemUnlockedUsers,
+ mSystemUnlockedUsers.length);
+
+ mDisks.clear();
+ mVolumes.clear();
+
+ addInternalVolumeLocked();
+ }
+
+ try {
+ // TODO(b/135341433): Remove paranoid logging when FUSE is stable
+ Slog.i(TAG, "Resetting vold...");
+ mVold.reset();
+ Slog.i(TAG, "Reset vold");
+
+ // Tell vold about all existing and started users
+ for (UserInfo user : users) {
+ mVold.onUserAdded(user.id, user.serialNumber);
+ }
+ for (int userId : systemUnlockedUsers) {
+ mVold.onUserStarted(userId);
+ mStoraged.onUserStarted(userId);
+ }
+ if (mIsAutomotive) {
+ restoreAllUnlockedUsers(userManager, users, systemUnlockedUsers);
+ }
+ mVold.onSecureKeyguardStateChanged(mSecureKeyguardShowing);
+ mStorageManagerInternal.onReset(mVold);
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+ }
+ }
+
+ private void restoreAllUnlockedUsers(UserManager userManager, List<UserInfo> allUsers,
+ int[] systemUnlockedUsers) throws Exception {
+ Arrays.sort(systemUnlockedUsers);
+ UserManager.invalidateIsUserUnlockedCache();
+ for (UserInfo user : allUsers) {
+ int userId = user.id;
+ if (!userManager.isUserRunning(userId)) {
+ continue;
+ }
+ if (Arrays.binarySearch(systemUnlockedUsers, userId) >= 0) {
+ continue;
+ }
+ boolean unlockingOrUnlocked = userManager.isUserUnlockingOrUnlocked(userId);
+ if (!unlockingOrUnlocked) {
+ continue;
+ }
+ Slog.w(TAG, "UNLOCK_USER lost from vold reset, will retry, user:" + userId);
+ mVold.onUserStarted(userId);
+ mStoraged.onUserStarted(userId);
+ mHandler.obtainMessage(H_COMPLETE_UNLOCK_USER, userId).sendToTarget();
+ }
+ }
+
+ private void onUnlockUser(int userId) {
+ Slog.d(TAG, "onUnlockUser " + userId);
+
+ // We purposefully block here to make sure that user-specific
+ // staging area is ready so it's ready for zygote-forked apps to
+ // bind mount against.
+ try {
+ mStorageSessionController.onUnlockUser(userId);
+ mVold.onUserStarted(userId);
+ mStoraged.onUserStarted(userId);
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+
+ mHandler.obtainMessage(H_COMPLETE_UNLOCK_USER, userId).sendToTarget();
+ }
+
+ private void completeUnlockUser(int userId) {
+ // If user 0 has completed unlock, perform a one-time migration of legacy obb data
+ // to its new location. This may take time depending on the size of the data to be copied
+ // so it's done on the StorageManager handler thread.
+ if (userId == 0) {
+ mPmInternal.migrateLegacyObbData();
+ }
+
+ onKeyguardStateChanged(false);
+
+ // Record user as started so newly mounted volumes kick off events
+ // correctly, then synthesize events for any already-mounted volumes.
+ synchronized (mLock) {
+ if (mIsAutomotive) {
+ for (int unlockedUser : mSystemUnlockedUsers) {
+ if (unlockedUser == userId) {
+ // This can happen as restoreAllUnlockedUsers can double post the message.
+ Log.i(TAG, "completeUnlockUser called for already unlocked user:" + userId);
+ return;
+ }
+ }
+ }
+ for (int i = 0; i < mVolumes.size(); i++) {
+ final VolumeInfo vol = mVolumes.valueAt(i);
+ if (vol.isVisibleForRead(userId) && vol.isMountedReadable()) {
+ final StorageVolume userVol = vol.buildStorageVolume(mContext, userId, false);
+ mHandler.obtainMessage(H_VOLUME_BROADCAST, userVol).sendToTarget();
+
+ final String envState = VolumeInfo.getEnvironmentForState(vol.getState());
+ mCallbacks.notifyStorageStateChanged(userVol.getPath(), envState, envState);
+ }
+ }
+ mSystemUnlockedUsers = ArrayUtils.appendInt(mSystemUnlockedUsers, userId);
+ }
+ }
+
+ private void onCleanupUser(int userId) {
+ Slog.d(TAG, "onCleanupUser " + userId);
+
+ try {
+ mVold.onUserStopped(userId);
+ mStoraged.onUserStopped(userId);
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+
+ synchronized (mLock) {
+ mSystemUnlockedUsers = ArrayUtils.removeInt(mSystemUnlockedUsers, userId);
+ }
+ }
+
+ private void onStopUser(int userId) {
+ Slog.i(TAG, "onStopUser " + userId);
+ try {
+ mStorageSessionController.onUserStopping(userId);
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+ PackageMonitor monitor = mPackageMonitorsForUser.remove(userId);
+ if (monitor != null) {
+ monitor.unregister();
+ }
+ }
+
+ private boolean supportsBlockCheckpoint() throws RemoteException {
+ enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
+ return mVold.supportsBlockCheckpoint();
+ }
+
+ @Override
+ public void onAwakeStateChanged(boolean isAwake) {
+ // Ignored
+ }
+
+ @Override
+ public void onKeyguardStateChanged(boolean isShowing) {
+ // Push down current secure keyguard status so that we ignore malicious
+ // USB devices while locked.
+ mSecureKeyguardShowing = isShowing
+ && mContext.getSystemService(KeyguardManager.class).isDeviceSecure(mCurrentUserId);
+ try {
+ mVold.onSecureKeyguardStateChanged(mSecureKeyguardShowing);
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+ }
+
+ void runIdleMaintenance(Runnable callback) {
+ mHandler.sendMessage(mHandler.obtainMessage(H_FSTRIM, callback));
+ }
+
+ // Binder entry point for kicking off an immediate fstrim
+ @Override
+ public void runMaintenance() {
+ enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
+ runIdleMaintenance(null);
+ }
+
+ @Override
+ public long lastMaintenance() {
+ return mLastMaintenance;
+ }
+
+ public void onDaemonConnected() {
+ mDaemonConnected = true;
+ mHandler.obtainMessage(H_DAEMON_CONNECTED).sendToTarget();
+ }
+
+ private void handleDaemonConnected() {
+ initIfBootedAndConnected();
+ resetIfBootedAndConnected();
+
+ // On an encrypted device we can't see system properties yet, so pull
+ // the system locale out of the mount service.
+ if ("".equals(VoldProperties.encrypt_progress().orElse(""))) {
+ copyLocaleFromMountService();
+ }
+ }
+
+ private void copyLocaleFromMountService() {
+ String systemLocale;
+ try {
+ systemLocale = getField(StorageManager.SYSTEM_LOCALE_KEY);
+ } catch (RemoteException e) {
+ return;
+ }
+ if (TextUtils.isEmpty(systemLocale)) {
+ return;
+ }
+
+ Slog.d(TAG, "Got locale " + systemLocale + " from mount service");
+ Locale locale = Locale.forLanguageTag(systemLocale);
+ Configuration config = new Configuration();
+ config.setLocale(locale);
+ try {
+ ActivityManager.getService().updatePersistentConfiguration(config);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error setting system locale from mount service", e);
+ }
+
+ // Temporary workaround for http://b/17945169.
+ Slog.d(TAG, "Setting system properties to " + systemLocale + " from mount service");
+ SystemProperties.set("persist.sys.locale", locale.toLanguageTag());
+ }
+
+ private final IVoldListener mListener = new IVoldListener.Stub() {
+ @Override
+ public void onDiskCreated(String diskId, int flags) {
+ synchronized (mLock) {
+ final String value = SystemProperties.get(StorageManager.PROP_ADOPTABLE);
+ switch (value) {
+ case "force_on":
+ flags |= DiskInfo.FLAG_ADOPTABLE;
+ break;
+ case "force_off":
+ flags &= ~DiskInfo.FLAG_ADOPTABLE;
+ break;
+ }
+ mDisks.put(diskId, new DiskInfo(diskId, flags));
+ }
+ }
+
+ @Override
+ public void onDiskScanned(String diskId) {
+ synchronized (mLock) {
+ final DiskInfo disk = mDisks.get(diskId);
+ if (disk != null) {
+ onDiskScannedLocked(disk);
+ }
+ }
+ }
+
+ @Override
+ public void onDiskMetadataChanged(String diskId, long sizeBytes, String label,
+ String sysPath) {
+ synchronized (mLock) {
+ final DiskInfo disk = mDisks.get(diskId);
+ if (disk != null) {
+ disk.size = sizeBytes;
+ disk.label = label;
+ disk.sysPath = sysPath;
+ }
+ }
+ }
+
+ @Override
+ public void onDiskDestroyed(String diskId) {
+ synchronized (mLock) {
+ final DiskInfo disk = mDisks.remove(diskId);
+ if (disk != null) {
+ mCallbacks.notifyDiskDestroyed(disk);
+ }
+ }
+ }
+
+ @Override
+ public void onVolumeCreated(String volId, int type, String diskId, String partGuid,
+ int userId) {
+ synchronized (mLock) {
+ final DiskInfo disk = mDisks.get(diskId);
+ final VolumeInfo vol = new VolumeInfo(volId, type, disk, partGuid);
+ vol.mountUserId = userId;
+ mVolumes.put(volId, vol);
+ onVolumeCreatedLocked(vol);
+ }
+ }
+
+ @Override
+ public void onVolumeStateChanged(String volId, int state) {
+ synchronized (mLock) {
+ final VolumeInfo vol = mVolumes.get(volId);
+ if (vol != null) {
+ final int oldState = vol.state;
+ final int newState = state;
+ vol.state = newState;
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = vol;
+ args.arg2 = oldState;
+ args.arg3 = newState;
+ mHandler.obtainMessage(H_VOLUME_STATE_CHANGED, args).sendToTarget();
+ onVolumeStateChangedLocked(vol, oldState, newState);
+ }
+ }
+ }
+
+ @Override
+ public void onVolumeMetadataChanged(String volId, String fsType, String fsUuid,
+ String fsLabel) {
+ synchronized (mLock) {
+ final VolumeInfo vol = mVolumes.get(volId);
+ if (vol != null) {
+ vol.fsType = fsType;
+ vol.fsUuid = fsUuid;
+ vol.fsLabel = fsLabel;
+ }
+ }
+ }
+
+ @Override
+ public void onVolumePathChanged(String volId, String path) {
+ synchronized (mLock) {
+ final VolumeInfo vol = mVolumes.get(volId);
+ if (vol != null) {
+ vol.path = path;
+ }
+ }
+ }
+
+ @Override
+ public void onVolumeInternalPathChanged(String volId, String internalPath) {
+ synchronized (mLock) {
+ final VolumeInfo vol = mVolumes.get(volId);
+ if (vol != null) {
+ vol.internalPath = internalPath;
+ }
+ }
+ }
+
+ @Override
+ public void onVolumeDestroyed(String volId) {
+ VolumeInfo vol = null;
+ synchronized (mLock) {
+ vol = mVolumes.remove(volId);
+ }
+
+ if (vol != null) {
+ mStorageSessionController.onVolumeRemove(vol);
+ try {
+ if (vol.type == VolumeInfo.TYPE_PRIVATE) {
+ mInstaller.onPrivateVolumeRemoved(vol.getFsUuid());
+ }
+ } catch (Installer.InstallerException e) {
+ Slog.i(TAG, "Failed when private volume unmounted " + vol, e);
+ }
+ }
+ }
+ };
+
+ @GuardedBy("mLock")
+ private void onDiskScannedLocked(DiskInfo disk) {
+ int volumeCount = 0;
+ for (int i = 0; i < mVolumes.size(); i++) {
+ final VolumeInfo vol = mVolumes.valueAt(i);
+ if (Objects.equals(disk.id, vol.getDiskId())) {
+ volumeCount++;
+ }
+ }
+
+ final Intent intent = new Intent(DiskInfo.ACTION_DISK_SCANNED);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+ intent.putExtra(DiskInfo.EXTRA_DISK_ID, disk.id);
+ intent.putExtra(DiskInfo.EXTRA_VOLUME_COUNT, volumeCount);
+ mHandler.obtainMessage(H_INTERNAL_BROADCAST, intent).sendToTarget();
+
+ final CountDownLatch latch = mDiskScanLatches.remove(disk.id);
+ if (latch != null) {
+ latch.countDown();
+ }
+
+ disk.volumeCount = volumeCount;
+ mCallbacks.notifyDiskScanned(disk, volumeCount);
+ }
+
+ @GuardedBy("mLock")
+ private void onVolumeCreatedLocked(VolumeInfo vol) {
+ if (mPmInternal.isOnlyCoreApps()) {
+ Slog.d(TAG, "System booted in core-only mode; ignoring volume " + vol.getId());
+ return;
+ }
+ final ActivityManagerInternal amInternal =
+ LocalServices.getService(ActivityManagerInternal.class);
+
+ if (mIsFuseEnabled && vol.mountUserId >= 0
+ && !amInternal.isUserRunning(vol.mountUserId, 0)) {
+ Slog.d(TAG, "Ignoring volume " + vol.getId() + " because user "
+ + Integer.toString(vol.mountUserId) + " is no longer running.");
+ return;
+ }
+
+ if (vol.type == VolumeInfo.TYPE_EMULATED) {
+ final StorageManager storage = mContext.getSystemService(StorageManager.class);
+ final VolumeInfo privateVol = storage.findPrivateForEmulated(vol);
+
+ if (Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, mPrimaryStorageUuid)
+ && VolumeInfo.ID_PRIVATE_INTERNAL.equals(privateVol.id)) {
+ Slog.v(TAG, "Found primary storage at " + vol);
+ vol.mountFlags |= VolumeInfo.MOUNT_FLAG_PRIMARY;
+ vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE;
+ mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
+
+ } else if (Objects.equals(privateVol.fsUuid, mPrimaryStorageUuid)) {
+ Slog.v(TAG, "Found primary storage at " + vol);
+ vol.mountFlags |= VolumeInfo.MOUNT_FLAG_PRIMARY;
+ vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE;
+ mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
+ }
+
+ } else if (vol.type == VolumeInfo.TYPE_PUBLIC) {
+ // TODO: only look at first public partition
+ if (Objects.equals(StorageManager.UUID_PRIMARY_PHYSICAL, mPrimaryStorageUuid)
+ && vol.disk.isDefaultPrimary()) {
+ Slog.v(TAG, "Found primary storage at " + vol);
+ vol.mountFlags |= VolumeInfo.MOUNT_FLAG_PRIMARY;
+ vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE;
+ }
+
+ // Adoptable public disks are visible to apps, since they meet
+ // public API requirement of being in a stable location.
+ if (vol.disk.isAdoptable()) {
+ vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE;
+ } else if (vol.disk.isSd()) {
+ vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE;
+ }
+
+ vol.mountUserId = mCurrentUserId;
+ mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
+
+ } else if (vol.type == VolumeInfo.TYPE_PRIVATE) {
+ mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
+
+ } else if (vol.type == VolumeInfo.TYPE_STUB) {
+ vol.mountUserId = mCurrentUserId;
+ mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
+ } else {
+ Slog.d(TAG, "Skipping automatic mounting of " + vol);
+ }
+ }
+
+ private boolean isBroadcastWorthy(VolumeInfo vol) {
+ switch (vol.getType()) {
+ case VolumeInfo.TYPE_PRIVATE:
+ case VolumeInfo.TYPE_PUBLIC:
+ case VolumeInfo.TYPE_EMULATED:
+ case VolumeInfo.TYPE_STUB:
+ break;
+ default:
+ return false;
+ }
+
+ switch (vol.getState()) {
+ case VolumeInfo.STATE_MOUNTED:
+ case VolumeInfo.STATE_MOUNTED_READ_ONLY:
+ case VolumeInfo.STATE_EJECTING:
+ case VolumeInfo.STATE_UNMOUNTED:
+ case VolumeInfo.STATE_UNMOUNTABLE:
+ case VolumeInfo.STATE_BAD_REMOVAL:
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+ }
+
+
+ private void onVolumeStateChangedLocked(VolumeInfo vol, int oldState, int newState) {
+ if (vol.type == VolumeInfo.TYPE_EMULATED) {
+ if (newState != VolumeInfo.STATE_MOUNTED) {
+ mFuseMountedUser.remove(vol.getMountUserId());
+ } else if (mVoldAppDataIsolationEnabled){
+ final int userId = vol.getMountUserId();
+ mFuseMountedUser.add(userId);
+ // Async remount app storage so it won't block the main thread.
+ new Thread(() -> {
+ Map<Integer, String> pidPkgMap = null;
+ // getProcessesWithPendingBindMounts() could fail when a new app process is
+ // starting and it's not planning to mount storage dirs in zygote, but it's
+ // rare, so we retry 5 times and hope we can get the result successfully.
+ for (int i = 0; i < 5; i++) {
+ try {
+ pidPkgMap = LocalServices.getService(ActivityManagerInternal.class)
+ .getProcessesWithPendingBindMounts(vol.getMountUserId());
+ break;
+ } catch (IllegalStateException e) {
+ Slog.i(TAG, "Some processes are starting, retry");
+ // Wait 100ms and retry so hope the pending process is started.
+ SystemClock.sleep(100);
+ }
+ }
+ if (pidPkgMap != null) {
+ remountAppStorageDirs(pidPkgMap, userId);
+ } else {
+ Slog.wtf(TAG, "Not able to getStorageNotOptimizedProcesses() after"
+ + " 5 retries");
+ }
+ }).start();
+ }
+ }
+ }
+
+
+ private void onVolumeStateChangedAsync(VolumeInfo vol, int oldState, int newState) {
+ synchronized (mLock) {
+ // Remember that we saw this volume so we're ready to accept user
+ // metadata, or so we can annoy them when a private volume is ejected
+ if (!TextUtils.isEmpty(vol.fsUuid)) {
+ VolumeRecord rec = mRecords.get(vol.fsUuid);
+ if (rec == null) {
+ rec = new VolumeRecord(vol.type, vol.fsUuid);
+ rec.partGuid = vol.partGuid;
+ rec.createdMillis = System.currentTimeMillis();
+ if (vol.type == VolumeInfo.TYPE_PRIVATE) {
+ rec.nickname = vol.disk.getDescription();
+ }
+ mRecords.put(rec.fsUuid, rec);
+ } else {
+ // Handle upgrade case where we didn't store partition GUID
+ if (TextUtils.isEmpty(rec.partGuid)) {
+ rec.partGuid = vol.partGuid;
+ }
+ }
+
+ rec.lastSeenMillis = System.currentTimeMillis();
+ writeSettingsLocked();
+ }
+ }
+
+ if (newState == VolumeInfo.STATE_MOUNTED) {
+ // Private volumes can be unmounted and re-mounted even after a user has
+ // been unlocked; on devices that support encryption keys tied to the filesystem,
+ // this requires setting up the keys again.
+ prepareUserStorageIfNeeded(vol);
+ }
+
+ // This is a blocking call to Storage Service which needs to process volume state changed
+ // before notifying other listeners.
+ // Intentionally called without the mLock to avoid deadlocking from the Storage Service.
+ try {
+ mStorageSessionController.notifyVolumeStateChanged(vol);
+ } catch (ExternalStorageServiceException e) {
+ Log.e(TAG, "Failed to notify volume state changed to the Storage Service", e);
+ }
+ synchronized (mLock) {
+ mCallbacks.notifyVolumeStateChanged(vol, oldState, newState);
+
+ // Do not broadcast before boot has completed to avoid launching the
+ // processes that receive the intent unnecessarily.
+ if (mBootCompleted && isBroadcastWorthy(vol)) {
+ final Intent intent = new Intent(VolumeInfo.ACTION_VOLUME_STATE_CHANGED);
+ intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.id);
+ intent.putExtra(VolumeInfo.EXTRA_VOLUME_STATE, newState);
+ intent.putExtra(VolumeRecord.EXTRA_FS_UUID, vol.fsUuid);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+ mHandler.obtainMessage(H_INTERNAL_BROADCAST, intent).sendToTarget();
+ }
+
+ final String oldStateEnv = VolumeInfo.getEnvironmentForState(oldState);
+ final String newStateEnv = VolumeInfo.getEnvironmentForState(newState);
+
+ if (!Objects.equals(oldStateEnv, newStateEnv)) {
+ // Kick state changed event towards all started users. Any users
+ // started after this point will trigger additional
+ // user-specific broadcasts.
+ for (int userId : mSystemUnlockedUsers) {
+ if (vol.isVisibleForRead(userId)) {
+ final StorageVolume userVol = vol.buildStorageVolume(mContext, userId,
+ false);
+ mHandler.obtainMessage(H_VOLUME_BROADCAST, userVol).sendToTarget();
+
+ mCallbacks.notifyStorageStateChanged(userVol.getPath(), oldStateEnv,
+ newStateEnv);
+ }
+ }
+ }
+
+ if ((vol.type == VolumeInfo.TYPE_PUBLIC || vol.type == VolumeInfo.TYPE_STUB)
+ && vol.state == VolumeInfo.STATE_EJECTING) {
+ // TODO: this should eventually be handled by new ObbVolume state changes
+ /*
+ * Some OBBs might have been unmounted when this volume was
+ * unmounted, so send a message to the handler to let it know to
+ * remove those from the list of mounted OBBS.
+ */
+ mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(
+ OBB_FLUSH_MOUNT_STATE, vol.path));
+ }
+ maybeLogMediaMount(vol, newState);
+ }
+ }
+
+ private void maybeLogMediaMount(VolumeInfo vol, int newState) {
+ if (!SecurityLog.isLoggingEnabled()) {
+ return;
+ }
+
+ final DiskInfo disk = vol.getDisk();
+ if (disk == null || (disk.flags & (DiskInfo.FLAG_SD | DiskInfo.FLAG_USB)) == 0) {
+ return;
+ }
+
+ // Sometimes there is a newline character.
+ final String label = disk.label != null ? disk.label.trim() : "";
+
+ if (newState == VolumeInfo.STATE_MOUNTED
+ || newState == VolumeInfo.STATE_MOUNTED_READ_ONLY) {
+ SecurityLog.writeEvent(SecurityLog.TAG_MEDIA_MOUNT, vol.path, label);
+ } else if (newState == VolumeInfo.STATE_UNMOUNTED
+ || newState == VolumeInfo.STATE_BAD_REMOVAL) {
+ SecurityLog.writeEvent(SecurityLog.TAG_MEDIA_UNMOUNT, vol.path, label);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void onMoveStatusLocked(int status) {
+ if (mMoveCallback == null) {
+ Slog.w(TAG, "Odd, status but no move requested");
+ return;
+ }
+
+ // TODO: estimate remaining time
+ try {
+ mMoveCallback.onStatusChanged(-1, status, -1);
+ } catch (RemoteException ignored) {
+ }
+
+ // We've finished copying and we're about to clean up old data, so
+ // remember that move was successful if we get rebooted
+ if (status == MOVE_STATUS_COPY_FINISHED) {
+ Slog.d(TAG, "Move to " + mMoveTargetUuid + " copy phase finshed; persisting");
+
+ mPrimaryStorageUuid = mMoveTargetUuid;
+ writeSettingsLocked();
+ }
+
+ if (PackageManager.isMoveStatusFinished(status)) {
+ Slog.d(TAG, "Move to " + mMoveTargetUuid + " finished with status " + status);
+
+ mMoveCallback = null;
+ mMoveTargetUuid = null;
+ }
+ }
+
+ private void enforcePermission(String perm) {
+ mContext.enforceCallingOrSelfPermission(perm, perm);
+ }
+
+ /**
+ * Decide if volume is mountable per device policies.
+ */
+ private boolean isMountDisallowed(VolumeInfo vol) {
+ UserManager userManager = mContext.getSystemService(UserManager.class);
+
+ boolean isUsbRestricted = false;
+ if (vol.disk != null && vol.disk.isUsb()) {
+ isUsbRestricted = userManager.hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER,
+ Binder.getCallingUserHandle());
+ }
+
+ boolean isTypeRestricted = false;
+ if (vol.type == VolumeInfo.TYPE_PUBLIC || vol.type == VolumeInfo.TYPE_PRIVATE
+ || vol.type == VolumeInfo.TYPE_STUB) {
+ isTypeRestricted = userManager
+ .hasUserRestriction(UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA,
+ Binder.getCallingUserHandle());
+ }
+
+ return isUsbRestricted || isTypeRestricted;
+ }
+
+ private void enforceAdminUser() {
+ UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ final int callingUserId = UserHandle.getCallingUserId();
+ boolean isAdmin;
+ long token = Binder.clearCallingIdentity();
+ try {
+ isAdmin = um.getUserInfo(callingUserId).isAdmin();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ if (!isAdmin) {
+ throw new SecurityException("Only admin users can adopt sd cards");
+ }
+ }
+
+ /**
+ * Constructs a new StorageManagerService instance
+ *
+ * @param context Binder context for this service
+ */
+ public StorageManagerService(Context context) {
+ sSelf = this;
+
+ // Snapshot feature flag used for this boot
+ SystemProperties.set(StorageManager.PROP_ISOLATED_STORAGE_SNAPSHOT, Boolean.toString(
+ SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, true)));
+
+ // If there is no value in the property yet (first boot after data wipe), this value may be
+ // incorrect until #updateFusePropFromSettings where we set the correct value and reboot if
+ // different
+ mIsFuseEnabled = SystemProperties.getBoolean(PROP_FUSE, DEFAULT_FUSE_ENABLED);
+ mVoldAppDataIsolationEnabled = mIsFuseEnabled && SystemProperties.getBoolean(
+ ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, false);
+ mContext = context;
+ mResolver = mContext.getContentResolver();
+ mCallbacks = new Callbacks(FgThread.get().getLooper());
+ mLockPatternUtils = new LockPatternUtils(mContext);
+
+ HandlerThread hthread = new HandlerThread(TAG);
+ hthread.start();
+ mHandler = new StorageManagerServiceHandler(hthread.getLooper());
+
+ // Add OBB Action Handler to StorageManagerService thread.
+ mObbActionHandler = new ObbActionHandler(IoThread.get().getLooper());
+
+ mStorageSessionController = new StorageSessionController(mContext, mIsFuseEnabled);
+
+ mInstaller = new Installer(mContext);
+ mInstaller.onStart();
+
+ // Initialize the last-fstrim tracking if necessary
+ File dataDir = Environment.getDataDirectory();
+ File systemDir = new File(dataDir, "system");
+ mLastMaintenanceFile = new File(systemDir, LAST_FSTRIM_FILE);
+ if (!mLastMaintenanceFile.exists()) {
+ // Not setting mLastMaintenance here means that we will force an
+ // fstrim during reboot following the OTA that installs this code.
+ try {
+ (new FileOutputStream(mLastMaintenanceFile)).close();
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to create fstrim record " + mLastMaintenanceFile.getPath());
+ }
+ } else {
+ mLastMaintenance = mLastMaintenanceFile.lastModified();
+ }
+
+ mSettingsFile = new AtomicFile(
+ new File(Environment.getDataSystemDirectory(), "storage.xml"), "storage-settings");
+
+ synchronized (mLock) {
+ readSettingsLocked();
+ }
+
+ LocalServices.addService(StorageManagerInternal.class, mStorageManagerInternal);
+
+ final IntentFilter userFilter = new IntentFilter();
+ userFilter.addAction(Intent.ACTION_USER_ADDED);
+ userFilter.addAction(Intent.ACTION_USER_REMOVED);
+ mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler);
+
+ synchronized (mLock) {
+ addInternalVolumeLocked();
+ }
+
+ // Add ourself to the Watchdog monitors if enabled.
+ if (WATCHDOG_ENABLE) {
+ Watchdog.getInstance().addMonitor(this);
+ }
+
+ mIsAutomotive = context.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_AUTOMOTIVE);
+ }
+
+ /**
+ * Checks if user changed the persistent settings_fuse flag from Settings UI
+ * and updates PROP_FUSE (reboots if changed).
+ */
+ private void updateFusePropFromSettings() {
+ boolean settingsFuseFlag = SystemProperties.getBoolean(PROP_SETTINGS_FUSE,
+ DEFAULT_FUSE_ENABLED);
+ Slog.d(TAG, "FUSE flags. Settings: " + settingsFuseFlag
+ + ". Default: " + DEFAULT_FUSE_ENABLED);
+
+ if (mIsFuseEnabled != settingsFuseFlag) {
+ Slog.i(TAG, "Toggling persist.sys.fuse to " + settingsFuseFlag);
+ // Set prop_fuse to match prop_settings_fuse because it is used by native daemons like
+ // init, zygote, installd and vold
+ SystemProperties.set(PROP_FUSE, Boolean.toString(settingsFuseFlag));
+ // Then perform hard reboot to kick policy into place
+ mContext.getSystemService(PowerManager.class).reboot("fuse_prop");
+ }
+ }
+
+ private void start() {
+ connectStoraged();
+ connectVold();
+ }
+
+ private void connectStoraged() {
+ IBinder binder = ServiceManager.getService("storaged");
+ if (binder != null) {
+ try {
+ binder.linkToDeath(new DeathRecipient() {
+ @Override
+ public void binderDied() {
+ Slog.w(TAG, "storaged died; reconnecting");
+ mStoraged = null;
+ connectStoraged();
+ }
+ }, 0);
+ } catch (RemoteException e) {
+ binder = null;
+ }
+ }
+
+ if (binder != null) {
+ mStoraged = IStoraged.Stub.asInterface(binder);
+ } else {
+ Slog.w(TAG, "storaged not found; trying again");
+ }
+
+ if (mStoraged == null) {
+ BackgroundThread.getHandler().postDelayed(() -> {
+ connectStoraged();
+ }, DateUtils.SECOND_IN_MILLIS);
+ } else {
+ onDaemonConnected();
+ }
+ }
+
+ private void connectVold() {
+ IBinder binder = ServiceManager.getService("vold");
+ if (binder != null) {
+ try {
+ binder.linkToDeath(new DeathRecipient() {
+ @Override
+ public void binderDied() {
+ Slog.w(TAG, "vold died; reconnecting");
+ mVold = null;
+ connectVold();
+ }
+ }, 0);
+ } catch (RemoteException e) {
+ binder = null;
+ }
+ }
+
+ if (binder != null) {
+ mVold = IVold.Stub.asInterface(binder);
+ try {
+ mVold.setListener(mListener);
+ } catch (RemoteException e) {
+ mVold = null;
+ Slog.w(TAG, "vold listener rejected; trying again", e);
+ }
+ } else {
+ Slog.w(TAG, "vold not found; trying again");
+ }
+
+ if (mVold == null) {
+ BackgroundThread.getHandler().postDelayed(() -> {
+ connectVold();
+ }, DateUtils.SECOND_IN_MILLIS);
+ } else {
+ onDaemonConnected();
+ }
+ }
+
+ private void servicesReady() {
+ mPmInternal = LocalServices.getService(PackageManagerInternal.class);
+
+ mIPackageManager = IPackageManager.Stub.asInterface(
+ ServiceManager.getService("package"));
+ mIAppOpsService = IAppOpsService.Stub.asInterface(
+ ServiceManager.getService(Context.APP_OPS_SERVICE));
+
+ ProviderInfo provider = getProviderInfo(MediaStore.AUTHORITY);
+ if (provider != null) {
+ mMediaStoreAuthorityAppId = UserHandle.getAppId(provider.applicationInfo.uid);
+ sMediaStoreAuthorityProcessName = provider.applicationInfo.processName;
+ }
+
+ provider = getProviderInfo(Downloads.Impl.AUTHORITY);
+ if (provider != null) {
+ mDownloadsAuthorityAppId = UserHandle.getAppId(provider.applicationInfo.uid);
+ }
+
+ provider = getProviderInfo(DocumentsContract.EXTERNAL_STORAGE_PROVIDER_AUTHORITY);
+ if (provider != null) {
+ mExternalStorageAuthorityAppId = UserHandle.getAppId(provider.applicationInfo.uid);
+ }
+
+ if (!mIsFuseEnabled) {
+ try {
+ mIAppOpsService.startWatchingMode(OP_REQUEST_INSTALL_PACKAGES, null,
+ mAppOpsCallback);
+ mIAppOpsService.startWatchingMode(OP_LEGACY_STORAGE, null, mAppOpsCallback);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ private ProviderInfo getProviderInfo(String authority) {
+ return mPmInternal.resolveContentProvider(
+ authority, PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+ UserHandle.getUserId(UserHandle.USER_SYSTEM));
+ }
+
+ private void updateLegacyStorageApps(String packageName, int uid, boolean hasLegacy) {
+ synchronized (mLock) {
+ if (hasLegacy) {
+ Slog.v(TAG, "Package " + packageName + " has legacy storage");
+ mUidsWithLegacyExternalStorage.add(uid);
+ } else {
+ // TODO(b/149391976): Handle shared user id. Check if there's any other
+ // installed app with legacy external storage before removing
+ Slog.v(TAG, "Package " + packageName + " does not have legacy storage");
+ mUidsWithLegacyExternalStorage.remove(uid);
+ }
+ }
+ }
+
+ private void snapshotAndMonitorLegacyStorageAppOp(UserHandle user) {
+ int userId = user.getIdentifier();
+
+ // TODO(b/149391976): Use mIAppOpsService.getPackagesForOps instead of iterating below
+ // It should improve performance but the AppOps method doesn't return any app here :(
+ // This operation currently takes about ~20ms on a freshly flashed device
+ for (ApplicationInfo ai : mPmInternal.getInstalledApplications(MATCH_DIRECT_BOOT_AWARE
+ | MATCH_DIRECT_BOOT_UNAWARE | MATCH_UNINSTALLED_PACKAGES | MATCH_ANY_USER,
+ userId, Process.myUid())) {
+ try {
+ boolean hasLegacy = mIAppOpsService.checkOperation(OP_LEGACY_STORAGE, ai.uid,
+ ai.packageName) == MODE_ALLOWED;
+ updateLegacyStorageApps(ai.packageName, ai.uid, hasLegacy);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to check legacy op for package " + ai.packageName, e);
+ }
+ }
+
+ PackageMonitor monitor = new PackageMonitor() {
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ updateLegacyStorageApps(packageName, uid, false);
+ }
+ };
+ // TODO(b/149391976): Use different handler?
+ monitor.register(mContext, user, true, mHandler);
+ mPackageMonitorsForUser.put(userId, monitor);
+ }
+
+ private static long getLastAccessTime(AppOpsManager manager,
+ int uid, String packageName, int[] ops) {
+ long maxTime = 0;
+ final List<AppOpsManager.PackageOps> pkgs = manager.getOpsForPackage(uid, packageName, ops);
+ for (AppOpsManager.PackageOps pkg : CollectionUtils.emptyIfNull(pkgs)) {
+ for (AppOpsManager.OpEntry op : CollectionUtils.emptyIfNull(pkg.getOps())) {
+ maxTime = Math.max(maxTime, op.getLastAccessTime(
+ AppOpsManager.OP_FLAGS_ALL_TRUSTED));
+ }
+ }
+ return maxTime;
+ }
+
+ private void systemReady() {
+ LocalServices.getService(ActivityTaskManagerInternal.class)
+ .registerScreenObserver(this);
+
+ mHandler.obtainMessage(H_SYSTEM_READY).sendToTarget();
+ }
+
+ private void bootCompleted() {
+ mBootCompleted = true;
+ mHandler.obtainMessage(H_BOOT_COMPLETED).sendToTarget();
+ updateFusePropFromSettings();
+ }
+
+ private void handleBootCompleted() {
+ initIfBootedAndConnected();
+ resetIfBootedAndConnected();
+ }
+
+ private String getDefaultPrimaryStorageUuid() {
+ if (SystemProperties.getBoolean(StorageManager.PROP_PRIMARY_PHYSICAL, false)) {
+ return StorageManager.UUID_PRIMARY_PHYSICAL;
+ } else {
+ return StorageManager.UUID_PRIVATE_INTERNAL;
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void readSettingsLocked() {
+ mRecords.clear();
+ mPrimaryStorageUuid = getDefaultPrimaryStorageUuid();
+
+ FileInputStream fis = null;
+ try {
+ fis = mSettingsFile.openRead();
+ final XmlPullParser in = Xml.newPullParser();
+ in.setInput(fis, StandardCharsets.UTF_8.name());
+
+ int type;
+ while ((type = in.next()) != END_DOCUMENT) {
+ if (type == START_TAG) {
+ final String tag = in.getName();
+ if (TAG_VOLUMES.equals(tag)) {
+ final int version = readIntAttribute(in, ATTR_VERSION, VERSION_INIT);
+ final boolean primaryPhysical = SystemProperties.getBoolean(
+ StorageManager.PROP_PRIMARY_PHYSICAL, false);
+ final boolean validAttr = (version >= VERSION_FIX_PRIMARY)
+ || (version >= VERSION_ADD_PRIMARY && !primaryPhysical);
+ if (validAttr) {
+ mPrimaryStorageUuid = readStringAttribute(in,
+ ATTR_PRIMARY_STORAGE_UUID);
+ }
+ } else if (TAG_VOLUME.equals(tag)) {
+ final VolumeRecord rec = readVolumeRecord(in);
+ mRecords.put(rec.fsUuid, rec);
+ }
+ }
+ }
+ } catch (FileNotFoundException e) {
+ // Missing metadata is okay, probably first boot
+ } catch (IOException e) {
+ Slog.wtf(TAG, "Failed reading metadata", e);
+ } catch (XmlPullParserException e) {
+ Slog.wtf(TAG, "Failed reading metadata", e);
+ } finally {
+ IoUtils.closeQuietly(fis);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void writeSettingsLocked() {
+ FileOutputStream fos = null;
+ try {
+ fos = mSettingsFile.startWrite();
+
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(fos, StandardCharsets.UTF_8.name());
+ out.startDocument(null, true);
+ out.startTag(null, TAG_VOLUMES);
+ writeIntAttribute(out, ATTR_VERSION, VERSION_FIX_PRIMARY);
+ writeStringAttribute(out, ATTR_PRIMARY_STORAGE_UUID, mPrimaryStorageUuid);
+ final int size = mRecords.size();
+ for (int i = 0; i < size; i++) {
+ final VolumeRecord rec = mRecords.valueAt(i);
+ writeVolumeRecord(out, rec);
+ }
+ out.endTag(null, TAG_VOLUMES);
+ out.endDocument();
+
+ mSettingsFile.finishWrite(fos);
+ } catch (IOException e) {
+ if (fos != null) {
+ mSettingsFile.failWrite(fos);
+ }
+ }
+ }
+
+ public static VolumeRecord readVolumeRecord(XmlPullParser in) throws IOException {
+ final int type = readIntAttribute(in, ATTR_TYPE);
+ final String fsUuid = readStringAttribute(in, ATTR_FS_UUID);
+ final VolumeRecord meta = new VolumeRecord(type, fsUuid);
+ meta.partGuid = readStringAttribute(in, ATTR_PART_GUID);
+ meta.nickname = readStringAttribute(in, ATTR_NICKNAME);
+ meta.userFlags = readIntAttribute(in, ATTR_USER_FLAGS);
+ meta.createdMillis = readLongAttribute(in, ATTR_CREATED_MILLIS, 0);
+ meta.lastSeenMillis = readLongAttribute(in, ATTR_LAST_SEEN_MILLIS, 0);
+ meta.lastTrimMillis = readLongAttribute(in, ATTR_LAST_TRIM_MILLIS, 0);
+ meta.lastBenchMillis = readLongAttribute(in, ATTR_LAST_BENCH_MILLIS, 0);
+ return meta;
+ }
+
+ public static void writeVolumeRecord(XmlSerializer out, VolumeRecord rec) throws IOException {
+ out.startTag(null, TAG_VOLUME);
+ writeIntAttribute(out, ATTR_TYPE, rec.type);
+ writeStringAttribute(out, ATTR_FS_UUID, rec.fsUuid);
+ writeStringAttribute(out, ATTR_PART_GUID, rec.partGuid);
+ writeStringAttribute(out, ATTR_NICKNAME, rec.nickname);
+ writeIntAttribute(out, ATTR_USER_FLAGS, rec.userFlags);
+ writeLongAttribute(out, ATTR_CREATED_MILLIS, rec.createdMillis);
+ writeLongAttribute(out, ATTR_LAST_SEEN_MILLIS, rec.lastSeenMillis);
+ writeLongAttribute(out, ATTR_LAST_TRIM_MILLIS, rec.lastTrimMillis);
+ writeLongAttribute(out, ATTR_LAST_BENCH_MILLIS, rec.lastBenchMillis);
+ out.endTag(null, TAG_VOLUME);
+ }
+
+ /**
+ * Exposed API calls below here
+ */
+
+ @Override
+ public void registerListener(IStorageEventListener listener) {
+ mCallbacks.register(listener);
+ }
+
+ @Override
+ public void unregisterListener(IStorageEventListener listener) {
+ mCallbacks.unregister(listener);
+ }
+
+ @Override
+ public void shutdown(final IStorageShutdownObserver observer) {
+ enforcePermission(android.Manifest.permission.SHUTDOWN);
+
+ Slog.i(TAG, "Shutting down");
+ mHandler.obtainMessage(H_SHUTDOWN, observer).sendToTarget();
+ }
+
+ @Override
+ public void mount(String volId) {
+ enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
+
+ final VolumeInfo vol = findVolumeByIdOrThrow(volId);
+ if (isMountDisallowed(vol)) {
+ throw new SecurityException("Mounting " + volId + " restricted by policy");
+ }
+
+ mount(vol);
+ }
+
+ private void remountAppStorageDirs(Map<Integer, String> pidPkgMap, int userId) {
+ for (Entry<Integer, String> entry : pidPkgMap.entrySet()) {
+ final int pid = entry.getKey();
+ final String packageName = entry.getValue();
+ Slog.i(TAG, "Remounting storage for pid: " + pid);
+ final String[] sharedPackages =
+ mPmInternal.getSharedUserPackagesForPackage(packageName, userId);
+ final int uid = mPmInternal.getPackageUidInternal(packageName, 0, userId);
+ final String[] packages =
+ sharedPackages.length != 0 ? sharedPackages : new String[]{packageName};
+ try {
+ mVold.remountAppStorageDirs(uid, pid, packages);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+ }
+
+ private void mount(VolumeInfo vol) {
+ try {
+ // TODO(b/135341433): Remove paranoid logging when FUSE is stable
+ Slog.i(TAG, "Mounting volume " + vol);
+ mVold.mount(vol.id, vol.mountFlags, vol.mountUserId, new IVoldMountCallback.Stub() {
+ @Override
+ public boolean onVolumeChecking(FileDescriptor fd, String path,
+ String internalPath) {
+ vol.path = path;
+ vol.internalPath = internalPath;
+ ParcelFileDescriptor pfd = new ParcelFileDescriptor(fd);
+ try {
+ mStorageSessionController.onVolumeMount(pfd, vol);
+ return true;
+ } catch (ExternalStorageServiceException e) {
+ Slog.e(TAG, "Failed to mount volume " + vol, e);
+
+ int nextResetSeconds = FAILED_MOUNT_RESET_TIMEOUT_SECONDS;
+ Slog.i(TAG, "Scheduling reset in " + nextResetSeconds + "s");
+ mHandler.removeMessages(H_RESET);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(H_RESET),
+ TimeUnit.SECONDS.toMillis(nextResetSeconds));
+ return false;
+ } finally {
+ try {
+ pfd.close();
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to close FUSE device fd", e);
+ }
+ }
+ }
+ });
+ Slog.i(TAG, "Mounted volume " + vol);
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+ }
+
+ @Override
+ public void unmount(String volId) {
+ enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
+
+ final VolumeInfo vol = findVolumeByIdOrThrow(volId);
+ unmount(vol);
+ }
+
+ private void unmount(VolumeInfo vol) {
+ try {
+ try {
+ if (vol.type == VolumeInfo.TYPE_PRIVATE) {
+ mInstaller.onPrivateVolumeRemoved(vol.getFsUuid());
+ }
+ } catch (Installer.InstallerException e) {
+ Slog.e(TAG, "Failed unmount mirror data", e);
+ }
+ mVold.unmount(vol.id);
+ mStorageSessionController.onVolumeUnmount(vol);
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+ }
+
+ @Override
+ public void format(String volId) {
+ enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
+
+ final VolumeInfo vol = findVolumeByIdOrThrow(volId);
+ final String fsUuid = vol.fsUuid;
+ try {
+ mVold.format(vol.id, "auto");
+
+ // After a successful format above, we should forget about any
+ // records for the old partition, since it'll never appear again
+ if (!TextUtils.isEmpty(fsUuid)) {
+ forgetVolume(fsUuid);
+ }
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+ }
+
+ @Override
+ public void benchmark(String volId, IVoldTaskListener listener) {
+ enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
+
+ try {
+ mVold.benchmark(volId, new IVoldTaskListener.Stub() {
+ @Override
+ public void onStatus(int status, PersistableBundle extras) {
+ dispatchOnStatus(listener, status, extras);
+ }
+
+ @Override
+ public void onFinished(int status, PersistableBundle extras) {
+ dispatchOnFinished(listener, status, extras);
+
+ final String path = extras.getString("path");
+ final String ident = extras.getString("ident");
+ final long create = extras.getLong("create");
+ final long run = extras.getLong("run");
+ final long destroy = extras.getLong("destroy");
+
+ final DropBoxManager dropBox = mContext.getSystemService(DropBoxManager.class);
+ dropBox.addText(TAG_STORAGE_BENCHMARK, scrubPath(path)
+ + " " + ident + " " + create + " " + run + " " + destroy);
+
+ synchronized (mLock) {
+ final VolumeRecord rec = findRecordForPath(path);
+ if (rec != null) {
+ rec.lastBenchMillis = System.currentTimeMillis();
+ writeSettingsLocked();
+ }
+ }
+ }
+ });
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
+ public void partitionPublic(String diskId) {
+ enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
+
+ final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
+ try {
+ mVold.partition(diskId, IVold.PARTITION_TYPE_PUBLIC, -1);
+ waitForLatch(latch, "partitionPublic", 3 * DateUtils.MINUTE_IN_MILLIS);
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+ }
+
+ @Override
+ public void partitionPrivate(String diskId) {
+ enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
+ enforceAdminUser();
+
+ final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
+ try {
+ mVold.partition(diskId, IVold.PARTITION_TYPE_PRIVATE, -1);
+ waitForLatch(latch, "partitionPrivate", 3 * DateUtils.MINUTE_IN_MILLIS);
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+ }
+
+ @Override
+ public void partitionMixed(String diskId, int ratio) {
+ enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
+ enforceAdminUser();
+
+ final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
+ try {
+ mVold.partition(diskId, IVold.PARTITION_TYPE_MIXED, ratio);
+ waitForLatch(latch, "partitionMixed", 3 * DateUtils.MINUTE_IN_MILLIS);
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+ }
+
+ @Override
+ public void setVolumeNickname(String fsUuid, String nickname) {
+ enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
+
+ Objects.requireNonNull(fsUuid);
+ synchronized (mLock) {
+ final VolumeRecord rec = mRecords.get(fsUuid);
+ rec.nickname = nickname;
+ mCallbacks.notifyVolumeRecordChanged(rec);
+ writeSettingsLocked();
+ }
+ }
+
+ @Override
+ public void setVolumeUserFlags(String fsUuid, int flags, int mask) {
+ enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
+
+ Objects.requireNonNull(fsUuid);
+ synchronized (mLock) {
+ final VolumeRecord rec = mRecords.get(fsUuid);
+ rec.userFlags = (rec.userFlags & ~mask) | (flags & mask);
+ mCallbacks.notifyVolumeRecordChanged(rec);
+ writeSettingsLocked();
+ }
+ }
+
+ @Override
+ public void forgetVolume(String fsUuid) {
+ enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
+
+ Objects.requireNonNull(fsUuid);
+
+ synchronized (mLock) {
+ final VolumeRecord rec = mRecords.remove(fsUuid);
+ if (rec != null && !TextUtils.isEmpty(rec.partGuid)) {
+ mHandler.obtainMessage(H_PARTITION_FORGET, rec).sendToTarget();
+ }
+ mCallbacks.notifyVolumeForgotten(fsUuid);
+
+ // If this had been primary storage, revert back to internal and
+ // reset vold so we bind into new volume into place.
+ if (Objects.equals(mPrimaryStorageUuid, fsUuid)) {
+ mPrimaryStorageUuid = getDefaultPrimaryStorageUuid();
+ mHandler.obtainMessage(H_RESET).sendToTarget();
+ }
+
+ writeSettingsLocked();
+ }
+ }
+
+ @Override
+ public void forgetAllVolumes() {
+ enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
+
+ synchronized (mLock) {
+ for (int i = 0; i < mRecords.size(); i++) {
+ final String fsUuid = mRecords.keyAt(i);
+ final VolumeRecord rec = mRecords.valueAt(i);
+ if (!TextUtils.isEmpty(rec.partGuid)) {
+ mHandler.obtainMessage(H_PARTITION_FORGET, rec).sendToTarget();
+ }
+ mCallbacks.notifyVolumeForgotten(fsUuid);
+ }
+ mRecords.clear();
+
+ if (!Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, mPrimaryStorageUuid)) {
+ mPrimaryStorageUuid = getDefaultPrimaryStorageUuid();
+ }
+
+ writeSettingsLocked();
+ mHandler.obtainMessage(H_RESET).sendToTarget();
+ }
+ }
+
+ private void forgetPartition(String partGuid, String fsUuid) {
+ try {
+ mVold.forgetPartition(partGuid, fsUuid);
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+ }
+
+ @Override
+ public void fstrim(int flags, IVoldTaskListener listener) {
+ enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
+
+ try {
+ // Block based checkpoint process runs fstrim. So, if checkpoint is in progress
+ // (first boot after OTA), We skip idle maintenance and make sure the last
+ // fstrim time is still updated. If file based checkpoints are used, we run
+ // idle maintenance (GC + fstrim) regardless of checkpoint status.
+ if (!needsCheckpoint() || !supportsBlockCheckpoint()) {
+ mVold.fstrim(flags, new IVoldTaskListener.Stub() {
+ @Override
+ public void onStatus(int status, PersistableBundle extras) {
+ dispatchOnStatus(listener, status, extras);
+
+ // Ignore trim failures
+ if (status != 0) return;
+
+ final String path = extras.getString("path");
+ final long bytes = extras.getLong("bytes");
+ final long time = extras.getLong("time");
+
+ final DropBoxManager dropBox = mContext.getSystemService(DropBoxManager.class);
+ dropBox.addText(TAG_STORAGE_TRIM, scrubPath(path) + " " + bytes + " " + time);
+
+ synchronized (mLock) {
+ final VolumeRecord rec = findRecordForPath(path);
+ if (rec != null) {
+ rec.lastTrimMillis = System.currentTimeMillis();
+ writeSettingsLocked();
+ }
+ }
+ }
+
+ @Override
+ public void onFinished(int status, PersistableBundle extras) {
+ dispatchOnFinished(listener, status, extras);
+
+ // TODO: benchmark when desired
+ }
+ });
+ } else {
+ Slog.i(TAG, "Skipping fstrim - block based checkpoint in progress");
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ void runIdleMaint(Runnable callback) {
+ enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
+
+ try {
+ // Block based checkpoint process runs fstrim. So, if checkpoint is in progress
+ // (first boot after OTA), We skip idle maintenance and make sure the last
+ // fstrim time is still updated. If file based checkpoints are used, we run
+ // idle maintenance (GC + fstrim) regardless of checkpoint status.
+ if (!needsCheckpoint() || !supportsBlockCheckpoint()) {
+ mVold.runIdleMaint(new IVoldTaskListener.Stub() {
+ @Override
+ public void onStatus(int status, PersistableBundle extras) {
+ // Not currently used
+ }
+ @Override
+ public void onFinished(int status, PersistableBundle extras) {
+ if (callback != null) {
+ BackgroundThread.getHandler().post(callback);
+ }
+ }
+ });
+ } else {
+ Slog.i(TAG, "Skipping idle maintenance - block based checkpoint in progress");
+ }
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+ }
+
+ @Override
+ public void runIdleMaintenance() {
+ runIdleMaint(null);
+ }
+
+ void abortIdleMaint(Runnable callback) {
+ enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
+
+ try {
+ mVold.abortIdleMaint(new IVoldTaskListener.Stub() {
+ @Override
+ public void onStatus(int status, PersistableBundle extras) {
+ // Not currently used
+ }
+ @Override
+ public void onFinished(int status, PersistableBundle extras) {
+ if (callback != null) {
+ BackgroundThread.getHandler().post(callback);
+ }
+ }
+ });
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+ }
+
+ @Override
+ public void abortIdleMaintenance() {
+ abortIdleMaint(null);
+ }
+
+ private void remountUidExternalStorage(int uid, int mode) {
+ if (uid == Process.SYSTEM_UID) {
+ // No need to remount uid for system because it has all access anyways
+ return;
+ }
+
+ try {
+ mVold.remountUid(uid, mode);
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+ }
+
+ @Override
+ public void setDebugFlags(int flags, int mask) {
+ enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
+
+ if ((mask & StorageManager.DEBUG_EMULATE_FBE) != 0) {
+ if (!EMULATE_FBE_SUPPORTED) {
+ throw new IllegalStateException(
+ "Emulation not supported on this device");
+ }
+ if (StorageManager.isFileEncryptedNativeOnly()) {
+ throw new IllegalStateException(
+ "Emulation not supported on device with native FBE");
+ }
+ if (mLockPatternUtils.isCredentialRequiredToDecrypt(false)) {
+ throw new IllegalStateException(
+ "Emulation requires disabling 'Secure start-up' in Settings > Security");
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ final boolean emulateFbe = (flags & StorageManager.DEBUG_EMULATE_FBE) != 0;
+ SystemProperties.set(StorageManager.PROP_EMULATE_FBE, Boolean.toString(emulateFbe));
+
+ // Perform hard reboot to kick policy into place
+ mContext.getSystemService(PowerManager.class).reboot(null);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ if ((mask & (StorageManager.DEBUG_ADOPTABLE_FORCE_ON
+ | StorageManager.DEBUG_ADOPTABLE_FORCE_OFF)) != 0) {
+ final String value;
+ if ((flags & StorageManager.DEBUG_ADOPTABLE_FORCE_ON) != 0) {
+ value = "force_on";
+ } else if ((flags & StorageManager.DEBUG_ADOPTABLE_FORCE_OFF) != 0) {
+ value = "force_off";
+ } else {
+ value = "";
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ SystemProperties.set(StorageManager.PROP_ADOPTABLE, value);
+
+ // Reset storage to kick new setting into place
+ mHandler.obtainMessage(H_RESET).sendToTarget();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ if ((mask & (StorageManager.DEBUG_SDCARDFS_FORCE_ON
+ | StorageManager.DEBUG_SDCARDFS_FORCE_OFF)) != 0) {
+ final String value;
+ if ((flags & StorageManager.DEBUG_SDCARDFS_FORCE_ON) != 0) {
+ value = "force_on";
+ } else if ((flags & StorageManager.DEBUG_SDCARDFS_FORCE_OFF) != 0) {
+ value = "force_off";
+ } else {
+ value = "";
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ SystemProperties.set(StorageManager.PROP_SDCARDFS, value);
+
+ // Reset storage to kick new setting into place
+ mHandler.obtainMessage(H_RESET).sendToTarget();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ if ((mask & StorageManager.DEBUG_VIRTUAL_DISK) != 0) {
+ final boolean enabled = (flags & StorageManager.DEBUG_VIRTUAL_DISK) != 0;
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ SystemProperties.set(StorageManager.PROP_VIRTUAL_DISK, Boolean.toString(enabled));
+
+ // Reset storage to kick new setting into place
+ mHandler.obtainMessage(H_RESET).sendToTarget();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ if ((mask & (StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_ON
+ | StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_OFF)) != 0) {
+ final int value;
+ if ((flags & StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_ON) != 0) {
+ value = 1;
+ } else if ((flags & StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_OFF) != 0) {
+ value = -1;
+ } else {
+ value = 0;
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.ISOLATED_STORAGE_LOCAL, value);
+ refreshIsolatedStorageSettings();
+
+ // Perform hard reboot to kick policy into place
+ mHandler.post(() -> {
+ mContext.getSystemService(PowerManager.class).reboot(null);
+ });
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+
+ @Override
+ public String getPrimaryStorageUuid() {
+ synchronized (mLock) {
+ return mPrimaryStorageUuid;
+ }
+ }
+
+ @Override
+ public void setPrimaryStorageUuid(String volumeUuid, IPackageMoveObserver callback) {
+ enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
+
+ final VolumeInfo from;
+ final VolumeInfo to;
+
+ synchronized (mLock) {
+ if (Objects.equals(mPrimaryStorageUuid, volumeUuid)) {
+ throw new IllegalArgumentException("Primary storage already at " + volumeUuid);
+ }
+
+ if (mMoveCallback != null) {
+ throw new IllegalStateException("Move already in progress");
+ }
+ mMoveCallback = callback;
+ mMoveTargetUuid = volumeUuid;
+
+ // We need all the users unlocked to move their primary storage
+ final List<UserInfo> users = mContext.getSystemService(UserManager.class).getUsers();
+ for (UserInfo user : users) {
+ if (StorageManager.isFileEncryptedNativeOrEmulated()
+ && !isUserKeyUnlocked(user.id)) {
+ Slog.w(TAG, "Failing move due to locked user " + user.id);
+ onMoveStatusLocked(PackageManager.MOVE_FAILED_LOCKED_USER);
+ return;
+ }
+ }
+
+ // When moving to/from primary physical volume, we probably just nuked
+ // the current storage location, so we have nothing to move.
+ if (Objects.equals(StorageManager.UUID_PRIMARY_PHYSICAL, mPrimaryStorageUuid)
+ || Objects.equals(StorageManager.UUID_PRIMARY_PHYSICAL, volumeUuid)) {
+ Slog.d(TAG, "Skipping move to/from primary physical");
+ onMoveStatusLocked(MOVE_STATUS_COPY_FINISHED);
+ onMoveStatusLocked(PackageManager.MOVE_SUCCEEDED);
+ mHandler.obtainMessage(H_RESET).sendToTarget();
+ return;
+
+ } else {
+ from = findStorageForUuid(mPrimaryStorageUuid);
+ to = findStorageForUuid(volumeUuid);
+
+ if (from == null) {
+ Slog.w(TAG, "Failing move due to missing from volume " + mPrimaryStorageUuid);
+ onMoveStatusLocked(PackageManager.MOVE_FAILED_INTERNAL_ERROR);
+ return;
+ } else if (to == null) {
+ Slog.w(TAG, "Failing move due to missing to volume " + volumeUuid);
+ onMoveStatusLocked(PackageManager.MOVE_FAILED_INTERNAL_ERROR);
+ return;
+ }
+ }
+ }
+
+ try {
+ mVold.moveStorage(from.id, to.id, new IVoldTaskListener.Stub() {
+ @Override
+ public void onStatus(int status, PersistableBundle extras) {
+ synchronized (mLock) {
+ onMoveStatusLocked(status);
+ }
+ }
+
+ @Override
+ public void onFinished(int status, PersistableBundle extras) {
+ // Not currently used
+ }
+ });
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+ }
+
+ private void warnOnNotMounted() {
+ synchronized (mLock) {
+ for (int i = 0; i < mVolumes.size(); i++) {
+ final VolumeInfo vol = mVolumes.valueAt(i);
+ if (vol.isPrimary() && vol.isMountedWritable()) {
+ // Cool beans, we have a mounted primary volume
+ return;
+ }
+ }
+ }
+
+ Slog.w(TAG, "No primary storage mounted!");
+ }
+
+ private boolean isUidOwnerOfPackageOrSystem(String packageName, int callerUid) {
+ if (callerUid == android.os.Process.SYSTEM_UID) {
+ return true;
+ }
+
+ if (packageName == null) {
+ return false;
+ }
+
+ final int packageUid = mPmInternal.getPackageUid(packageName,
+ PackageManager.MATCH_DEBUG_TRIAGED_MISSING, UserHandle.getUserId(callerUid));
+
+ if (DEBUG_OBB) {
+ Slog.d(TAG, "packageName = " + packageName + ", packageUid = " +
+ packageUid + ", callerUid = " + callerUid);
+ }
+
+ return callerUid == packageUid;
+ }
+
+ @Override
+ public String getMountedObbPath(String rawPath) {
+ Objects.requireNonNull(rawPath, "rawPath cannot be null");
+
+ warnOnNotMounted();
+
+ final ObbState state;
+ synchronized (mObbMounts) {
+ state = mObbPathToStateMap.get(rawPath);
+ }
+ if (state == null) {
+ Slog.w(TAG, "Failed to find OBB mounted at " + rawPath);
+ return null;
+ }
+
+ return findVolumeByIdOrThrow(state.volId).getPath().getAbsolutePath();
+ }
+
+ @Override
+ public boolean isObbMounted(String rawPath) {
+ Objects.requireNonNull(rawPath, "rawPath cannot be null");
+ synchronized (mObbMounts) {
+ return mObbPathToStateMap.containsKey(rawPath);
+ }
+ }
+
+ @Override
+ public void mountObb(String rawPath, String canonicalPath, String key,
+ IObbActionListener token, int nonce, ObbInfo obbInfo) {
+ Objects.requireNonNull(rawPath, "rawPath cannot be null");
+ Objects.requireNonNull(canonicalPath, "canonicalPath cannot be null");
+ Objects.requireNonNull(token, "token cannot be null");
+ Objects.requireNonNull(obbInfo, "obbIfno cannot be null");
+
+ final int callingUid = Binder.getCallingUid();
+ final ObbState obbState = new ObbState(rawPath, canonicalPath,
+ callingUid, token, nonce, null);
+ final ObbAction action = new MountObbAction(obbState, key, callingUid, obbInfo);
+ mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
+
+ if (DEBUG_OBB)
+ Slog.i(TAG, "Send to OBB handler: " + action.toString());
+ }
+
+ @Override
+ public void unmountObb(String rawPath, boolean force, IObbActionListener token, int nonce) {
+ Objects.requireNonNull(rawPath, "rawPath cannot be null");
+
+ final ObbState existingState;
+ synchronized (mObbMounts) {
+ existingState = mObbPathToStateMap.get(rawPath);
+ }
+
+ if (existingState != null) {
+ // TODO: separate state object from request data
+ final int callingUid = Binder.getCallingUid();
+ final ObbState newState = new ObbState(rawPath, existingState.canonicalPath,
+ callingUid, token, nonce, existingState.volId);
+ final ObbAction action = new UnmountObbAction(newState, force);
+ mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
+
+ if (DEBUG_OBB)
+ Slog.i(TAG, "Send to OBB handler: " + action.toString());
+ } else {
+ Slog.w(TAG, "Unknown OBB mount at " + rawPath);
+ }
+ }
+
+ @Override
+ public int getEncryptionState() {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
+ "no permission to access the crypt keeper");
+
+ try {
+ return mVold.fdeComplete();
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ return StorageManager.ENCRYPTION_STATE_ERROR_UNKNOWN;
+ }
+ }
+
+ @Override
+ public int decryptStorage(String password) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
+ "no permission to access the crypt keeper");
+
+ if (TextUtils.isEmpty(password)) {
+ throw new IllegalArgumentException("password cannot be empty");
+ }
+
+ if (DEBUG_EVENTS) {
+ Slog.i(TAG, "decrypting storage...");
+ }
+
+ try {
+ mVold.fdeCheckPassword(password);
+ mHandler.postDelayed(() -> {
+ try {
+ mVold.fdeRestart();
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+ }, DateUtils.SECOND_IN_MILLIS);
+ return 0;
+ } catch (ServiceSpecificException e) {
+ Slog.e(TAG, "fdeCheckPassword failed", e);
+ return e.errorCode;
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ return StorageManager.ENCRYPTION_STATE_ERROR_UNKNOWN;
+ }
+ }
+
+ @Override
+ public int encryptStorage(int type, String password) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
+ "no permission to access the crypt keeper");
+
+ if (type == StorageManager.CRYPT_TYPE_DEFAULT) {
+ password = "";
+ } else if (TextUtils.isEmpty(password)) {
+ throw new IllegalArgumentException("password cannot be empty");
+ }
+
+ if (DEBUG_EVENTS) {
+ Slog.i(TAG, "encrypting storage...");
+ }
+
+ try {
+ mVold.fdeEnable(type, password, 0);
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ return -1;
+ }
+
+ return 0;
+ }
+
+ /** Set the password for encrypting the master key.
+ * @param type One of the CRYPTO_TYPE_XXX consts defined in StorageManager.
+ * @param password The password to set.
+ */
+ @Override
+ public int changeEncryptionPassword(int type, String password) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
+ "no permission to access the crypt keeper");
+
+ if (StorageManager.isFileEncryptedNativeOnly()) {
+ // Not supported on FBE devices
+ return -1;
+ }
+
+ if (type == StorageManager.CRYPT_TYPE_DEFAULT) {
+ password = "";
+ } else if (TextUtils.isEmpty(password)) {
+ throw new IllegalArgumentException("password cannot be empty");
+ }
+
+ if (DEBUG_EVENTS) {
+ Slog.i(TAG, "changing encryption password...");
+ }
+
+ try {
+ mVold.fdeChangePassword(type, password);
+ return 0;
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ return -1;
+ }
+ }
+
+ /**
+ * Validate a user-supplied password string with cryptfs
+ */
+ @Override
+ public int verifyEncryptionPassword(String password) throws RemoteException {
+ // Only the system process is permitted to validate passwords
+ if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) {
+ throw new SecurityException("no permission to access the crypt keeper");
+ }
+
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
+ "no permission to access the crypt keeper");
+
+ if (TextUtils.isEmpty(password)) {
+ throw new IllegalArgumentException("password cannot be empty");
+ }
+
+ if (DEBUG_EVENTS) {
+ Slog.i(TAG, "validating encryption password...");
+ }
+
+ try {
+ mVold.fdeVerifyPassword(password);
+ return 0;
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ return -1;
+ }
+ }
+
+ /**
+ * Get the type of encryption used to encrypt the master key.
+ * @return The type, one of the CRYPT_TYPE_XXX consts from StorageManager.
+ */
+ @Override
+ public int getPasswordType() {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
+ "no permission to access the crypt keeper");
+
+ try {
+ return mVold.fdeGetPasswordType();
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ return -1;
+ }
+ }
+
+ /**
+ * Set a field in the crypto header.
+ * @param field field to set
+ * @param contents contents to set in field
+ */
+ @Override
+ public void setField(String field, String contents) throws RemoteException {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
+ "no permission to access the crypt keeper");
+
+ if (!StorageManager.isBlockEncrypted()) {
+ // Only supported on FDE devices
+ return;
+ }
+
+ try {
+ mVold.fdeSetField(field, contents);
+ return;
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ return;
+ }
+ }
+
+ /**
+ * Gets a field from the crypto header.
+ * @param field field to get
+ * @return contents of field
+ */
+ @Override
+ public String getField(String field) throws RemoteException {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
+ "no permission to access the crypt keeper");
+
+ if (!StorageManager.isBlockEncrypted()) {
+ // Only supported on FDE devices
+ return null;
+ }
+
+ try {
+ return mVold.fdeGetField(field);
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ return null;
+ }
+ }
+
+ /**
+ * Is userdata convertible to file based encryption?
+ * @return non zero for convertible
+ */
+ @Override
+ public boolean isConvertibleToFBE() throws RemoteException {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
+ "no permission to access the crypt keeper");
+
+ try {
+ return mVold.isConvertibleToFbe();
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ return false;
+ }
+ }
+
+ /**
+ * Check whether the device supports filesystem checkpointing.
+ *
+ * @return true if the device supports filesystem checkpointing, false otherwise.
+ */
+ @Override
+ public boolean supportsCheckpoint() throws RemoteException {
+ return mVold.supportsCheckpoint();
+ }
+
+ /**
+ * Signal that checkpointing partitions should start a checkpoint on the next boot.
+ *
+ * @param numTries Number of times to try booting in checkpoint mode, before we will boot
+ * non-checkpoint mode and commit all changes immediately. Callers are
+ * responsible for ensuring that boot is safe (eg, by rolling back updates).
+ */
+ @Override
+ public void startCheckpoint(int numTries) throws RemoteException {
+ // Only the root, system_server and shell processes are permitted to start checkpoints
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.SYSTEM_UID && callingUid != Process.ROOT_UID
+ && callingUid != Process.SHELL_UID) {
+ throw new SecurityException("no permission to start filesystem checkpoint");
+ }
+
+ mVold.startCheckpoint(numTries);
+ }
+
+ /**
+ * Signal that checkpointing partitions should commit changes
+ */
+ @Override
+ public void commitChanges() throws RemoteException {
+ // Only the system process is permitted to commit checkpoints
+ if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) {
+ throw new SecurityException("no permission to commit checkpoint changes");
+ }
+
+ mVold.commitChanges();
+ }
+
+ /**
+ * Check if we should be mounting with checkpointing or are checkpointing now
+ */
+ @Override
+ public boolean needsCheckpoint() throws RemoteException {
+ enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
+ return mVold.needsCheckpoint();
+ }
+
+ /**
+ * Abort the current set of changes and either try again, or abort entirely
+ */
+ @Override
+ public void abortChanges(String message, boolean retry) throws RemoteException {
+ // Only the system process is permitted to abort checkpoints
+ if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) {
+ throw new SecurityException("no permission to commit checkpoint changes");
+ }
+
+ mVold.abortChanges(message, retry);
+ }
+
+ @Override
+ public String getPassword() throws RemoteException {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
+ "only keyguard can retrieve password");
+
+ try {
+ return mVold.fdeGetPassword();
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ return null;
+ }
+ }
+
+ @Override
+ public void clearPassword() throws RemoteException {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
+ "only keyguard can clear password");
+
+ try {
+ mVold.fdeClearPassword();
+ return;
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ return;
+ }
+ }
+
+ @Override
+ public void createUserKey(int userId, int serialNumber, boolean ephemeral) {
+ enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
+
+ try {
+ mVold.createUserKey(userId, serialNumber, ephemeral);
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+ }
+
+ @Override
+ public void destroyUserKey(int userId) {
+ enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
+
+ try {
+ mVold.destroyUserKey(userId);
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+ }
+
+ private String encodeBytes(byte[] bytes) {
+ if (ArrayUtils.isEmpty(bytes)) {
+ return "!";
+ } else {
+ return HexDump.toHexString(bytes);
+ }
+ }
+
+ /*
+ * Add this token/secret pair to the set of ways we can recover a disk encryption key.
+ * Changing the token/secret for a disk encryption key is done in two phases: first, adding
+ * a new token/secret pair with this call, then delting all other pairs with
+ * fixateNewestUserKeyAuth. This allows other places where a credential is used, such as
+ * Gatekeeper, to be updated between the two calls.
+ */
+ @Override
+ public void addUserKeyAuth(int userId, int serialNumber, byte[] token, byte[] secret) {
+ enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
+
+ try {
+ mVold.addUserKeyAuth(userId, serialNumber, encodeBytes(token), encodeBytes(secret));
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+ }
+
+ /*
+ * Clear disk encryption key bound to the associated token / secret pair. Removing the user
+ * binding of the Disk encryption key is done in two phases: first, this call will retrieve
+ * the disk encryption key using the provided token / secret pair and store it by
+ * encrypting it with a keymaster key not bound to the user, then fixateNewestUserKeyAuth
+ * is called to delete all other bindings of the disk encryption key.
+ */
+ @Override
+ public void clearUserKeyAuth(int userId, int serialNumber, byte[] token, byte[] secret) {
+ enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
+
+ try {
+ mVold.clearUserKeyAuth(userId, serialNumber, encodeBytes(token), encodeBytes(secret));
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+ }
+
+ /*
+ * Delete all disk encryption token/secret pairs except the most recently added one
+ */
+ @Override
+ public void fixateNewestUserKeyAuth(int userId) {
+ enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
+
+ try {
+ mVold.fixateNewestUserKeyAuth(userId);
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+ }
+
+ @Override
+ public void unlockUserKey(int userId, int serialNumber, byte[] token, byte[] secret) {
+ boolean isFsEncrypted = StorageManager.isFileEncryptedNativeOrEmulated();
+ Slog.d(TAG, "unlockUserKey: " + userId
+ + " isFileEncryptedNativeOrEmulated: " + isFsEncrypted
+ + " hasToken: " + (token != null)
+ + " hasSecret: " + (secret != null));
+ enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
+
+ if (isFsEncrypted) {
+ try {
+ mVold.unlockUserKey(userId, serialNumber, encodeBytes(token),
+ encodeBytes(secret));
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ return;
+ }
+ }
+
+ synchronized (mLock) {
+ mLocalUnlockedUsers.append(userId);
+ }
+ }
+
+ @Override
+ public void lockUserKey(int userId) {
+ enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
+
+ try {
+ mVold.lockUserKey(userId);
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ return;
+ }
+
+ synchronized (mLock) {
+ mLocalUnlockedUsers.remove(userId);
+ }
+ }
+
+ @Override
+ public boolean isUserKeyUnlocked(int userId) {
+ synchronized (mLock) {
+ return mLocalUnlockedUsers.contains(userId);
+ }
+ }
+
+ private boolean isSystemUnlocked(int userId) {
+ synchronized (mLock) {
+ return ArrayUtils.contains(mSystemUnlockedUsers, userId);
+ }
+ }
+
+ private void prepareUserStorageIfNeeded(VolumeInfo vol) {
+ if (vol.type != VolumeInfo.TYPE_PRIVATE) {
+ return;
+ }
+
+ final UserManager um = mContext.getSystemService(UserManager.class);
+ final UserManagerInternal umInternal =
+ LocalServices.getService(UserManagerInternal.class);
+
+ for (UserInfo user : um.getUsers(false /* includeDying */)) {
+ final int flags;
+ if (umInternal.isUserUnlockingOrUnlocked(user.id)) {
+ flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
+ } else if (umInternal.isUserRunning(user.id)) {
+ flags = StorageManager.FLAG_STORAGE_DE;
+ } else {
+ continue;
+ }
+
+ prepareUserStorageInternal(vol.fsUuid, user.id, user.serialNumber, flags);
+ }
+ }
+
+ @Override
+ public void prepareUserStorage(String volumeUuid, int userId, int serialNumber, int flags) {
+ enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
+
+ prepareUserStorageInternal(volumeUuid, userId, serialNumber, flags);
+ }
+
+ private void prepareUserStorageInternal(String volumeUuid, int userId, int serialNumber,
+ int flags) {
+ try {
+ mVold.prepareUserStorage(volumeUuid, userId, serialNumber, flags);
+ // After preparing user storage, we should check if we should mount data mirror again,
+ // and we do it for user 0 only as we only need to do once for all users.
+ if (volumeUuid != null) {
+ final StorageManager storage = mContext.getSystemService(StorageManager.class);
+ VolumeInfo info = storage.findVolumeByUuid(volumeUuid);
+ if (info != null && userId == 0 && info.type == VolumeInfo.TYPE_PRIVATE) {
+ mInstaller.tryMountDataMirror(volumeUuid);
+ }
+ }
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+ }
+
+ @Override
+ public void destroyUserStorage(String volumeUuid, int userId, int flags) {
+ enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
+
+ try {
+ mVold.destroyUserStorage(volumeUuid, userId, flags);
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+ }
+
+ @Override
+ public void fixupAppDir(String path) {
+ final Matcher matcher = KNOWN_APP_DIR_PATHS.matcher(path);
+ if (matcher.matches()) {
+ if (matcher.group(2) == null) {
+ Log.e(TAG, "Asked to fixup an app dir without a userId: " + path);
+ return;
+ }
+ try {
+ int userId = Integer.parseInt(matcher.group(2));
+ String packageName = matcher.group(3);
+ int uid = mContext.getPackageManager().getPackageUidAsUser(packageName, userId);
+ try {
+ mVold.fixupAppDir(path + "/", uid);
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.e(TAG, "Failed to fixup app dir for " + packageName, e);
+ }
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "Invalid userId in path: " + path, e);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Couldn't find package to fixup app dir " + path, e);
+ }
+ } else {
+ Log.e(TAG, "Path " + path + " is not a valid application-specific directory");
+ }
+ }
+
+ /** Not thread safe */
+ class AppFuseMountScope extends AppFuseBridge.MountScope {
+ private boolean mMounted = false;
+
+ public AppFuseMountScope(int uid, int mountId) {
+ super(uid, mountId);
+ }
+
+ @Override
+ public ParcelFileDescriptor open() throws NativeDaemonConnectorException {
+ try {
+ final FileDescriptor fd = mVold.mountAppFuse(uid, mountId);
+ mMounted = true;
+ return new ParcelFileDescriptor(fd);
+ } catch (Exception e) {
+ throw new NativeDaemonConnectorException("Failed to mount", e);
+ }
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(int mountId, int fileId, int flags)
+ throws NativeDaemonConnectorException {
+ try {
+ return new ParcelFileDescriptor(
+ mVold.openAppFuseFile(uid, mountId, fileId, flags));
+ } catch (Exception e) {
+ throw new NativeDaemonConnectorException("Failed to open", e);
+ }
+ }
+
+ @Override
+ public void close() throws Exception {
+ if (mMounted) {
+ mVold.unmountAppFuse(uid, mountId);
+ mMounted = false;
+ }
+ }
+ }
+
+ @Override
+ public @Nullable AppFuseMount mountProxyFileDescriptorBridge() {
+ Slog.v(TAG, "mountProxyFileDescriptorBridge");
+ final int uid = Binder.getCallingUid();
+
+ while (true) {
+ synchronized (mAppFuseLock) {
+ boolean newlyCreated = false;
+ if (mAppFuseBridge == null) {
+ mAppFuseBridge = new AppFuseBridge();
+ new Thread(mAppFuseBridge, AppFuseBridge.TAG).start();
+ newlyCreated = true;
+ }
+ try {
+ final int name = mNextAppFuseName++;
+ try {
+ return new AppFuseMount(
+ name, mAppFuseBridge.addBridge(new AppFuseMountScope(uid, name)));
+ } catch (FuseUnavailableMountException e) {
+ if (newlyCreated) {
+ // If newly created bridge fails, it's a real error.
+ Slog.e(TAG, "", e);
+ return null;
+ }
+ // It seems the thread of mAppFuseBridge has already been terminated.
+ mAppFuseBridge = null;
+ }
+ } catch (NativeDaemonConnectorException e) {
+ throw e.rethrowAsParcelableException();
+ }
+ }
+ }
+ }
+
+ @Override
+ public @Nullable ParcelFileDescriptor openProxyFileDescriptor(
+ int mountId, int fileId, int mode) {
+ Slog.v(TAG, "mountProxyFileDescriptor");
+
+ // We only support a narrow set of incoming mode flags
+ mode &= MODE_READ_WRITE;
+
+ try {
+ synchronized (mAppFuseLock) {
+ if (mAppFuseBridge == null) {
+ Slog.e(TAG, "FuseBridge has not been created");
+ return null;
+ }
+ return mAppFuseBridge.openFile(mountId, fileId, mode);
+ }
+ } catch (FuseUnavailableMountException | InterruptedException error) {
+ Slog.v(TAG, "The mount point has already been invalid", error);
+ return null;
+ }
+ }
+
+ @Override
+ public void mkdirs(String callingPkg, String appPath) {
+ final int callingUid = Binder.getCallingUid();
+ final int userId = UserHandle.getUserId(callingUid);
+ final String propertyName = "sys.user." + userId + ".ce_available";
+
+ // Ignore requests to create directories while storage is locked
+ if (!isUserKeyUnlocked(userId)) {
+ throw new IllegalStateException("Failed to prepare " + appPath);
+ }
+
+ // Ignore requests to create directories if CE storage is not available
+ if ((userId == UserHandle.USER_SYSTEM)
+ && !SystemProperties.getBoolean(propertyName, false)) {
+ throw new IllegalStateException("Failed to prepare " + appPath);
+ }
+
+ // Validate that reported package name belongs to caller
+ final AppOpsManager appOps = (AppOpsManager) mContext.getSystemService(
+ Context.APP_OPS_SERVICE);
+ appOps.checkPackage(callingUid, callingPkg);
+
+ File appFile = null;
+ try {
+ appFile = new File(appPath).getCanonicalFile();
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to resolve " + appPath + ": " + e);
+ }
+
+ appPath = appFile.getAbsolutePath();
+ if (!appPath.endsWith("/")) {
+ appPath = appPath + "/";
+ }
+ // Ensure that the path we're asked to create is a known application directory
+ // path.
+ final Matcher matcher = KNOWN_APP_DIR_PATHS.matcher(appPath);
+ if (matcher.matches()) {
+ // And that the package dir matches the calling package
+ if (!matcher.group(3).equals(callingPkg)) {
+ throw new SecurityException("Invalid mkdirs path: " + appFile
+ + " does not contain calling package " + callingPkg);
+ }
+ // And that the user id part of the path (if any) matches the calling user id,
+ // or if for a public volume (no user id), the user matches the current user
+ if ((matcher.group(2) != null && !matcher.group(2).equals(Integer.toString(userId)))
+ || (matcher.group(2) == null && userId != mCurrentUserId)) {
+ throw new SecurityException("Invalid mkdirs path: " + appFile
+ + " does not match calling user id " + userId);
+ }
+ try {
+ mVold.setupAppDir(appPath, callingUid);
+ } catch (RemoteException e) {
+ throw new IllegalStateException("Failed to prepare " + appPath + ": " + e);
+ }
+
+ return;
+ }
+ throw new SecurityException("Invalid mkdirs path: " + appFile
+ + " is not a known app path.");
+ }
+
+ @Override
+ public StorageVolume[] getVolumeList(int uid, String packageName, int flags) {
+ final int userId = UserHandle.getUserId(uid);
+
+ final boolean forWrite = (flags & StorageManager.FLAG_FOR_WRITE) != 0;
+ final boolean realState = (flags & StorageManager.FLAG_REAL_STATE) != 0;
+ final boolean includeInvisible = (flags & StorageManager.FLAG_INCLUDE_INVISIBLE) != 0;
+ final boolean includeRecent = (flags & StorageManager.FLAG_INCLUDE_RECENT) != 0;
+
+ // Report all volumes as unmounted until we've recorded that user 0 has unlocked. There
+ // are no guarantees that callers will see a consistent view of the volume before that
+ // point
+ final boolean systemUserUnlocked = isSystemUnlocked(UserHandle.USER_SYSTEM);
+
+ // When the caller is the app actually hosting external storage, we
+ // should never attempt to augment the actual storage volume state,
+ // otherwise we risk confusing it with race conditions as users go
+ // through various unlocked states
+ final boolean callerIsMediaStore = UserHandle.isSameApp(Binder.getCallingUid(),
+ mMediaStoreAuthorityAppId);
+
+ final boolean userIsDemo;
+ final boolean userKeyUnlocked;
+ final boolean storagePermission;
+ final long token = Binder.clearCallingIdentity();
+ try {
+ userIsDemo = LocalServices.getService(UserManagerInternal.class)
+ .getUserInfo(userId).isDemo();
+ userKeyUnlocked = isUserKeyUnlocked(userId);
+ storagePermission = mStorageManagerInternal.hasExternalStorage(uid, packageName);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+
+ boolean foundPrimary = false;
+
+ final ArrayList<StorageVolume> res = new ArrayList<>();
+ final ArraySet<String> resUuids = new ArraySet<>();
+ synchronized (mLock) {
+ for (int i = 0; i < mVolumes.size(); i++) {
+ final String volId = mVolumes.keyAt(i);
+ final VolumeInfo vol = mVolumes.valueAt(i);
+ switch (vol.getType()) {
+ case VolumeInfo.TYPE_PUBLIC:
+ case VolumeInfo.TYPE_STUB:
+ break;
+ case VolumeInfo.TYPE_EMULATED:
+ if (vol.getMountUserId() == userId) {
+ break;
+ }
+ // Skip if emulated volume not for userId
+ default:
+ continue;
+ }
+
+ boolean match = false;
+ if (forWrite) {
+ match = vol.isVisibleForWrite(userId);
+ } else {
+ match = vol.isVisibleForRead(userId)
+ || (includeInvisible && vol.getPath() != null);
+ }
+ if (!match) continue;
+
+ boolean reportUnmounted = false;
+ if (callerIsMediaStore) {
+ // When the caller is the app actually hosting external storage, we
+ // should never attempt to augment the actual storage volume state,
+ // otherwise we risk confusing it with race conditions as users go
+ // through various unlocked states
+ } else if (!systemUserUnlocked) {
+ reportUnmounted = true;
+ Slog.w(TAG, "Reporting " + volId + " unmounted due to system locked");
+ } else if ((vol.getType() == VolumeInfo.TYPE_EMULATED) && !userKeyUnlocked) {
+ reportUnmounted = true;
+ Slog.w(TAG, "Reporting " + volId + "unmounted due to " + userId + " locked");
+ } else if (!storagePermission && !realState) {
+ Slog.w(TAG, "Reporting " + volId + "unmounted due to missing permissions");
+ reportUnmounted = true;
+ }
+
+ final StorageVolume userVol = vol.buildStorageVolume(mContext, userId,
+ reportUnmounted);
+ if (vol.isPrimary()) {
+ res.add(0, userVol);
+ foundPrimary = true;
+ } else {
+ res.add(userVol);
+ }
+ resUuids.add(userVol.getUuid());
+ }
+
+ if (includeRecent) {
+ final long lastWeek = System.currentTimeMillis() - DateUtils.WEEK_IN_MILLIS;
+ for (int i = 0; i < mRecords.size(); i++) {
+ final VolumeRecord rec = mRecords.valueAt(i);
+
+ // Skip if we've already included it above
+ if (resUuids.contains(rec.fsUuid)) continue;
+
+ // Treat as recent if mounted within the last week
+ if (rec.lastSeenMillis > 0 && rec.lastSeenMillis < lastWeek) {
+ final StorageVolume userVol = rec.buildStorageVolume(mContext);
+ res.add(userVol);
+ resUuids.add(userVol.getUuid());
+ }
+ }
+ }
+ }
+
+ // Synthesize a volume for preloaded media under demo users, so that
+ // it's scanned into MediaStore
+ if (userIsDemo) {
+ final String id = "demo";
+ final File path = Environment.getDataPreloadsMediaDirectory();
+ final boolean primary = false;
+ final boolean removable = false;
+ final boolean emulated = true;
+ final boolean allowMassStorage = false;
+ final long maxFileSize = 0;
+ final UserHandle user = new UserHandle(userId);
+ final String envState = Environment.MEDIA_MOUNTED_READ_ONLY;
+ final String description = mContext.getString(android.R.string.unknownName);
+
+ res.add(new StorageVolume(id, path, path, description, primary, removable,
+ emulated, allowMassStorage, maxFileSize, user, id, envState));
+ }
+
+ if (!foundPrimary) {
+ Slog.w(TAG, "No primary storage defined yet; hacking together a stub");
+
+ final boolean primaryPhysical = SystemProperties.getBoolean(
+ StorageManager.PROP_PRIMARY_PHYSICAL, false);
+
+ final String id = "stub_primary";
+ final File path = Environment.getLegacyExternalStorageDirectory();
+ final String description = mContext.getString(android.R.string.unknownName);
+ final boolean primary = true;
+ final boolean removable = primaryPhysical;
+ final boolean emulated = !primaryPhysical;
+ final boolean allowMassStorage = false;
+ final long maxFileSize = 0L;
+ final UserHandle owner = new UserHandle(userId);
+ final String uuid = null;
+ final String state = Environment.MEDIA_REMOVED;
+
+ res.add(0, new StorageVolume(id, path, path,
+ description, primary, removable, emulated,
+ allowMassStorage, maxFileSize, owner, uuid, state));
+ }
+
+ return res.toArray(new StorageVolume[res.size()]);
+ }
+
+ @Override
+ public DiskInfo[] getDisks() {
+ synchronized (mLock) {
+ final DiskInfo[] res = new DiskInfo[mDisks.size()];
+ for (int i = 0; i < mDisks.size(); i++) {
+ res[i] = mDisks.valueAt(i);
+ }
+ return res;
+ }
+ }
+
+ @Override
+ public VolumeInfo[] getVolumes(int flags) {
+ synchronized (mLock) {
+ final VolumeInfo[] res = new VolumeInfo[mVolumes.size()];
+ for (int i = 0; i < mVolumes.size(); i++) {
+ res[i] = mVolumes.valueAt(i);
+ }
+ return res;
+ }
+ }
+
+ @Override
+ public VolumeRecord[] getVolumeRecords(int flags) {
+ synchronized (mLock) {
+ final VolumeRecord[] res = new VolumeRecord[mRecords.size()];
+ for (int i = 0; i < mRecords.size(); i++) {
+ res[i] = mRecords.valueAt(i);
+ }
+ return res;
+ }
+ }
+
+ @Override
+ public long getCacheQuotaBytes(String volumeUuid, int uid) {
+ if (uid != Binder.getCallingUid()) {
+ mContext.enforceCallingPermission(android.Manifest.permission.STORAGE_INTERNAL, TAG);
+ }
+ final long token = Binder.clearCallingIdentity();
+ final StorageStatsManager stats = mContext.getSystemService(StorageStatsManager.class);
+ try {
+ return stats.getCacheQuotaBytes(volumeUuid, uid);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public long getCacheSizeBytes(String volumeUuid, int uid) {
+ if (uid != Binder.getCallingUid()) {
+ mContext.enforceCallingPermission(android.Manifest.permission.STORAGE_INTERNAL, TAG);
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return mContext.getSystemService(StorageStatsManager.class)
+ .queryStatsForUid(volumeUuid, uid).getCacheBytes();
+ } catch (IOException e) {
+ throw new ParcelableException(e);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private int adjustAllocateFlags(int flags, int callingUid, String callingPackage) {
+ // Require permission to allocate aggressively
+ if ((flags & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.ALLOCATE_AGGRESSIVE, TAG);
+ }
+
+ // Apps normally can't directly defy reserved space
+ flags &= ~StorageManager.FLAG_ALLOCATE_DEFY_ALL_RESERVED;
+ flags &= ~StorageManager.FLAG_ALLOCATE_DEFY_HALF_RESERVED;
+
+ // However, if app is actively using the camera, then we're willing to
+ // clear up to half of the reserved cache space, since the user might be
+ // trying to capture an important memory.
+ final AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (appOps.isOperationActive(AppOpsManager.OP_CAMERA, callingUid, callingPackage)) {
+ Slog.d(TAG, "UID " + callingUid + " is actively using camera;"
+ + " letting them defy reserved cached data");
+ flags |= StorageManager.FLAG_ALLOCATE_DEFY_HALF_RESERVED;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+
+ return flags;
+ }
+
+ @Override
+ public long getAllocatableBytes(String volumeUuid, int flags, String callingPackage) {
+ flags = adjustAllocateFlags(flags, Binder.getCallingUid(), callingPackage);
+
+ final StorageManager storage = mContext.getSystemService(StorageManager.class);
+ final StorageStatsManager stats = mContext.getSystemService(StorageStatsManager.class);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ // In general, apps can allocate as much space as they want, except
+ // we never let them eat into either the minimum cache space or into
+ // the low disk warning space. To avoid user confusion, this logic
+ // should be kept in sync with getFreeBytes().
+ final File path = storage.findPathForUuid(volumeUuid);
+
+ long usable = 0;
+ long lowReserved = 0;
+ long fullReserved = 0;
+ long cacheClearable = 0;
+
+ if ((flags & StorageManager.FLAG_ALLOCATE_CACHE_ONLY) == 0) {
+ usable = path.getUsableSpace();
+ lowReserved = storage.getStorageLowBytes(path);
+ fullReserved = storage.getStorageFullBytes(path);
+ }
+
+ if ((flags & StorageManager.FLAG_ALLOCATE_NON_CACHE_ONLY) == 0
+ && stats.isQuotaSupported(volumeUuid)) {
+ final long cacheTotal = stats.getCacheBytes(volumeUuid);
+ final long cacheReserved = storage.getStorageCacheBytes(path, flags);
+ cacheClearable = Math.max(0, cacheTotal - cacheReserved);
+ }
+
+ if ((flags & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0) {
+ return Math.max(0, (usable + cacheClearable) - fullReserved);
+ } else {
+ return Math.max(0, (usable + cacheClearable) - lowReserved);
+ }
+ } catch (IOException e) {
+ throw new ParcelableException(e);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void allocateBytes(String volumeUuid, long bytes, int flags, String callingPackage) {
+ flags = adjustAllocateFlags(flags, Binder.getCallingUid(), callingPackage);
+
+ final long allocatableBytes = getAllocatableBytes(volumeUuid,
+ flags | StorageManager.FLAG_ALLOCATE_NON_CACHE_ONLY, callingPackage);
+ if (bytes > allocatableBytes) {
+ // If we don't have room without taking cache into account, check to see if we'd have
+ // room if we included freeable cache space.
+ final long cacheClearable = getAllocatableBytes(volumeUuid,
+ flags | StorageManager.FLAG_ALLOCATE_CACHE_ONLY, callingPackage);
+ if (bytes > allocatableBytes + cacheClearable) {
+ throw new ParcelableException(new IOException("Failed to allocate " + bytes
+ + " because only " + (allocatableBytes + cacheClearable) + " allocatable"));
+ }
+ }
+
+ final StorageManager storage = mContext.getSystemService(StorageManager.class);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ // Free up enough disk space to satisfy both the requested allocation
+ // and our low disk warning space.
+ final File path = storage.findPathForUuid(volumeUuid);
+ if ((flags & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0) {
+ bytes += storage.getStorageFullBytes(path);
+ } else {
+ bytes += storage.getStorageLowBytes(path);
+ }
+
+ mPmInternal.freeStorage(volumeUuid, bytes, flags);
+ } catch (IOException e) {
+ throw new ParcelableException(e);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private IAppOpsCallback.Stub mAppOpsCallback = new IAppOpsCallback.Stub() {
+ @Override
+ public void opChanged(int op, int uid, String packageName) throws RemoteException {
+ if (!ENABLE_ISOLATED_STORAGE) return;
+
+ int mountMode = getMountMode(uid, packageName);
+ boolean isUidActive = LocalServices.getService(ActivityManagerInternal.class)
+ .getUidProcessState(uid) != PROCESS_STATE_NONEXISTENT;
+
+ if (isUidActive) {
+ remountUidExternalStorage(uid, mountMode);
+ }
+ }
+ };
+
+ private void addObbStateLocked(ObbState obbState) throws RemoteException {
+ final IBinder binder = obbState.getBinder();
+ List<ObbState> obbStates = mObbMounts.get(binder);
+
+ if (obbStates == null) {
+ obbStates = new ArrayList<ObbState>();
+ mObbMounts.put(binder, obbStates);
+ } else {
+ for (final ObbState o : obbStates) {
+ if (o.rawPath.equals(obbState.rawPath)) {
+ throw new IllegalStateException("Attempt to add ObbState twice. "
+ + "This indicates an error in the StorageManagerService logic.");
+ }
+ }
+ }
+
+ obbStates.add(obbState);
+ try {
+ obbState.link();
+ } catch (RemoteException e) {
+ /*
+ * The binder died before we could link it, so clean up our state
+ * and return failure.
+ */
+ obbStates.remove(obbState);
+ if (obbStates.isEmpty()) {
+ mObbMounts.remove(binder);
+ }
+
+ // Rethrow the error so mountObb can get it
+ throw e;
+ }
+
+ mObbPathToStateMap.put(obbState.rawPath, obbState);
+ }
+
+ private void removeObbStateLocked(ObbState obbState) {
+ final IBinder binder = obbState.getBinder();
+ final List<ObbState> obbStates = mObbMounts.get(binder);
+ if (obbStates != null) {
+ if (obbStates.remove(obbState)) {
+ obbState.unlink();
+ }
+ if (obbStates.isEmpty()) {
+ mObbMounts.remove(binder);
+ }
+ }
+
+ mObbPathToStateMap.remove(obbState.rawPath);
+ }
+
+ private class ObbActionHandler extends Handler {
+
+ ObbActionHandler(Looper l) {
+ super(l);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case OBB_RUN_ACTION: {
+ final ObbAction action = (ObbAction) msg.obj;
+
+ if (DEBUG_OBB)
+ Slog.i(TAG, "OBB_RUN_ACTION: " + action.toString());
+
+ action.execute(this);
+ break;
+ }
+ case OBB_FLUSH_MOUNT_STATE: {
+ final String path = (String) msg.obj;
+
+ if (DEBUG_OBB)
+ Slog.i(TAG, "Flushing all OBB state for path " + path);
+
+ synchronized (mObbMounts) {
+ final List<ObbState> obbStatesToRemove = new LinkedList<ObbState>();
+
+ final Iterator<ObbState> i = mObbPathToStateMap.values().iterator();
+ while (i.hasNext()) {
+ final ObbState state = i.next();
+
+ /*
+ * If this entry's source file is in the volume path
+ * that got unmounted, remove it because it's no
+ * longer valid.
+ */
+ if (state.canonicalPath.startsWith(path)) {
+ obbStatesToRemove.add(state);
+ }
+ }
+
+ for (final ObbState obbState : obbStatesToRemove) {
+ if (DEBUG_OBB)
+ Slog.i(TAG, "Removing state for " + obbState.rawPath);
+
+ removeObbStateLocked(obbState);
+
+ try {
+ obbState.token.onObbResult(obbState.rawPath, obbState.nonce,
+ OnObbStateChangeListener.UNMOUNTED);
+ } catch (RemoteException e) {
+ Slog.i(TAG, "Couldn't send unmount notification for OBB: "
+ + obbState.rawPath);
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ private static class ObbException extends Exception {
+ public final int status;
+
+ public ObbException(int status, String message) {
+ super(message);
+ this.status = status;
+ }
+
+ public ObbException(int status, Throwable cause) {
+ super(cause.getMessage(), cause);
+ this.status = status;
+ }
+ }
+
+ abstract class ObbAction {
+
+ ObbState mObbState;
+
+ ObbAction(ObbState obbState) {
+ mObbState = obbState;
+ }
+
+ public void execute(ObbActionHandler handler) {
+ try {
+ if (DEBUG_OBB)
+ Slog.i(TAG, "Starting to execute action: " + toString());
+ handleExecute();
+ } catch (ObbException e) {
+ notifyObbStateChange(e);
+ }
+ }
+
+ abstract void handleExecute() throws ObbException;
+
+ protected void notifyObbStateChange(ObbException e) {
+ Slog.w(TAG, e);
+ notifyObbStateChange(e.status);
+ }
+
+ protected void notifyObbStateChange(int status) {
+ if (mObbState == null || mObbState.token == null) {
+ return;
+ }
+
+ try {
+ mObbState.token.onObbResult(mObbState.rawPath, mObbState.nonce, status);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "StorageEventListener went away while calling onObbStateChanged");
+ }
+ }
+ }
+
+ class MountObbAction extends ObbAction {
+ private final String mKey;
+ private final int mCallingUid;
+ private ObbInfo mObbInfo;
+
+ MountObbAction(ObbState obbState, String key, int callingUid, ObbInfo obbInfo) {
+ super(obbState);
+ mKey = key;
+ mCallingUid = callingUid;
+ mObbInfo = obbInfo;
+ }
+
+ @Override
+ public void handleExecute() throws ObbException {
+ warnOnNotMounted();
+
+ if (!isUidOwnerOfPackageOrSystem(mObbInfo.packageName, mCallingUid)) {
+ throw new ObbException(ERROR_PERMISSION_DENIED, "Denied attempt to mount OBB "
+ + mObbInfo.filename + " which is owned by " + mObbInfo.packageName);
+ }
+
+ final boolean isMounted;
+ synchronized (mObbMounts) {
+ isMounted = mObbPathToStateMap.containsKey(mObbState.rawPath);
+ }
+ if (isMounted) {
+ throw new ObbException(ERROR_ALREADY_MOUNTED,
+ "Attempt to mount OBB which is already mounted: " + mObbInfo.filename);
+ }
+
+ final String hashedKey;
+ final String binderKey;
+ if (mKey == null) {
+ hashedKey = "none";
+ binderKey = "";
+ } else {
+ try {
+ SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
+
+ KeySpec ks = new PBEKeySpec(mKey.toCharArray(), mObbInfo.salt,
+ PBKDF2_HASH_ROUNDS, CRYPTO_ALGORITHM_KEY_SIZE);
+ SecretKey key = factory.generateSecret(ks);
+ BigInteger bi = new BigInteger(key.getEncoded());
+ hashedKey = bi.toString(16);
+ binderKey = hashedKey;
+ } catch (GeneralSecurityException e) {
+ throw new ObbException(ERROR_INTERNAL, e);
+ }
+ }
+
+ try {
+ mObbState.volId = mVold.createObb(mObbState.canonicalPath, binderKey,
+ mObbState.ownerGid);
+ mVold.mount(mObbState.volId, 0, -1, null);
+
+ if (DEBUG_OBB)
+ Slog.d(TAG, "Successfully mounted OBB " + mObbState.canonicalPath);
+
+ synchronized (mObbMounts) {
+ addObbStateLocked(mObbState);
+ }
+
+ notifyObbStateChange(MOUNTED);
+ } catch (Exception e) {
+ throw new ObbException(ERROR_COULD_NOT_MOUNT, e);
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("MountObbAction{");
+ sb.append(mObbState);
+ sb.append('}');
+ return sb.toString();
+ }
+ }
+
+ class UnmountObbAction extends ObbAction {
+ private final boolean mForceUnmount;
+
+ UnmountObbAction(ObbState obbState, boolean force) {
+ super(obbState);
+ mForceUnmount = force;
+ }
+
+ @Override
+ public void handleExecute() throws ObbException {
+ warnOnNotMounted();
+
+ final ObbState existingState;
+ synchronized (mObbMounts) {
+ existingState = mObbPathToStateMap.get(mObbState.rawPath);
+ }
+
+ if (existingState == null) {
+ throw new ObbException(ERROR_NOT_MOUNTED, "Missing existingState");
+ }
+
+ if (existingState.ownerGid != mObbState.ownerGid) {
+ notifyObbStateChange(new ObbException(ERROR_PERMISSION_DENIED,
+ "Permission denied to unmount OBB " + existingState.rawPath
+ + " (owned by GID " + existingState.ownerGid + ")"));
+ return;
+ }
+
+ try {
+ mVold.unmount(mObbState.volId);
+ mVold.destroyObb(mObbState.volId);
+ mObbState.volId = null;
+
+ synchronized (mObbMounts) {
+ removeObbStateLocked(existingState);
+ }
+
+ notifyObbStateChange(UNMOUNTED);
+ } catch (Exception e) {
+ throw new ObbException(ERROR_COULD_NOT_UNMOUNT, e);
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("UnmountObbAction{");
+ sb.append(mObbState);
+ sb.append(",force=");
+ sb.append(mForceUnmount);
+ sb.append('}');
+ return sb.toString();
+ }
+ }
+
+ private void dispatchOnStatus(IVoldTaskListener listener, int status,
+ PersistableBundle extras) {
+ if (listener != null) {
+ try {
+ listener.onStatus(status, extras);
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
+
+ private void dispatchOnFinished(IVoldTaskListener listener, int status,
+ PersistableBundle extras) {
+ if (listener != null) {
+ try {
+ listener.onFinished(status, extras);
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
+
+ private int getMountMode(int uid, String packageName) {
+ final int mode = getMountModeInternal(uid, packageName);
+ if (LOCAL_LOGV) {
+ Slog.v(TAG, "Resolved mode " + mode + " for " + packageName + "/"
+ + UserHandle.formatUid(uid));
+ }
+ return mode;
+ }
+
+ private int getMountModeInternal(int uid, String packageName) {
+ try {
+ // Get some easy cases out of the way first
+ if (Process.isIsolated(uid)) {
+ return Zygote.MOUNT_EXTERNAL_NONE;
+ }
+
+ final String[] packagesForUid = mIPackageManager.getPackagesForUid(uid);
+ if (ArrayUtils.isEmpty(packagesForUid)) {
+ // It's possible the package got uninstalled already, so just ignore.
+ return Zygote.MOUNT_EXTERNAL_NONE;
+ }
+ if (packageName == null) {
+ packageName = packagesForUid[0];
+ }
+
+ if (mPmInternal.isInstantApp(packageName, UserHandle.getUserId(uid))) {
+ return Zygote.MOUNT_EXTERNAL_NONE;
+ }
+
+ if (mIsFuseEnabled && mStorageManagerInternal.isExternalStorageService(uid)) {
+ // Determine if caller requires pass_through mount; note that we do this for
+ // all processes that share a UID with MediaProvider; but this is fine, since
+ // those processes anyway share the same rights as MediaProvider.
+ return Zygote.MOUNT_EXTERNAL_PASS_THROUGH;
+ }
+
+ if (mIsFuseEnabled && (mDownloadsAuthorityAppId == UserHandle.getAppId(uid)
+ || mExternalStorageAuthorityAppId == UserHandle.getAppId(uid))) {
+ // DownloadManager can write in app-private directories on behalf of apps;
+ // give it write access to Android/
+ // ExternalStorageProvider can access Android/{data,obb} dirs in managed mode
+ return Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE;
+ }
+
+ final boolean hasMtp = mIPackageManager.checkUidPermission(ACCESS_MTP, uid) ==
+ PERMISSION_GRANTED;
+ if (mIsFuseEnabled && hasMtp) {
+ ApplicationInfo ai = mIPackageManager.getApplicationInfo(packageName,
+ 0, UserHandle.getUserId(uid));
+ if (ai != null && ai.isSignedWithPlatformKey()) {
+ // Platform processes hosting the MTP server should be able to write in Android/
+ return Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE;
+ }
+ }
+
+ // Determine if caller is holding runtime permission
+ final boolean hasRead = StorageManager.checkPermissionAndCheckOp(mContext, false, 0,
+ uid, packageName, READ_EXTERNAL_STORAGE, OP_READ_EXTERNAL_STORAGE);
+ final boolean hasWrite = StorageManager.checkPermissionAndCheckOp(mContext, false, 0,
+ uid, packageName, WRITE_EXTERNAL_STORAGE, OP_WRITE_EXTERNAL_STORAGE);
+
+ // We're only willing to give out broad access if they also hold
+ // runtime permission; this is a firm CDD requirement
+ final boolean hasFull = mIPackageManager.checkUidPermission(WRITE_MEDIA_STORAGE,
+ uid) == PERMISSION_GRANTED;
+ if (hasFull && hasWrite) {
+ return Zygote.MOUNT_EXTERNAL_FULL;
+ }
+
+ // We're only willing to give out installer access if they also hold
+ // runtime permission; this is a firm CDD requirement
+ final boolean hasInstall = mIPackageManager.checkUidPermission(INSTALL_PACKAGES,
+ uid) == PERMISSION_GRANTED;
+ boolean hasInstallOp = false;
+ // OP_REQUEST_INSTALL_PACKAGES is granted/denied per package but vold can't
+ // update mountpoints of a specific package. So, check the appop for all packages
+ // sharing the uid and allow same level of storage access for all packages even if
+ // one of the packages has the appop granted.
+ for (String uidPackageName : packagesForUid) {
+ if (mIAppOpsService.checkOperation(
+ OP_REQUEST_INSTALL_PACKAGES, uid, uidPackageName) == MODE_ALLOWED) {
+ hasInstallOp = true;
+ break;
+ }
+ }
+ if ((hasInstall || hasInstallOp) && hasWrite) {
+ return Zygote.MOUNT_EXTERNAL_INSTALLER;
+ }
+
+ // Otherwise we're willing to give out sandboxed or non-sandboxed if
+ // they hold the runtime permission
+ boolean hasLegacy = mIAppOpsService.checkOperation(OP_LEGACY_STORAGE,
+ uid, packageName) == MODE_ALLOWED;
+
+ if (hasLegacy && hasWrite) {
+ return Zygote.MOUNT_EXTERNAL_WRITE;
+ } else if (hasLegacy && hasRead) {
+ return Zygote.MOUNT_EXTERNAL_READ;
+ } else {
+ return Zygote.MOUNT_EXTERNAL_DEFAULT;
+ }
+ } catch (RemoteException e) {
+ // Should not happen
+ }
+ return Zygote.MOUNT_EXTERNAL_NONE;
+ }
+
+ private static class Callbacks extends Handler {
+ private static final int MSG_STORAGE_STATE_CHANGED = 1;
+ private static final int MSG_VOLUME_STATE_CHANGED = 2;
+ private static final int MSG_VOLUME_RECORD_CHANGED = 3;
+ private static final int MSG_VOLUME_FORGOTTEN = 4;
+ private static final int MSG_DISK_SCANNED = 5;
+ private static final int MSG_DISK_DESTROYED = 6;
+
+ private final RemoteCallbackList<IStorageEventListener>
+ mCallbacks = new RemoteCallbackList<>();
+
+ public Callbacks(Looper looper) {
+ super(looper);
+ }
+
+ public void register(IStorageEventListener callback) {
+ mCallbacks.register(callback);
+ }
+
+ public void unregister(IStorageEventListener callback) {
+ mCallbacks.unregister(callback);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ final SomeArgs args = (SomeArgs) msg.obj;
+ final int n = mCallbacks.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ final IStorageEventListener callback = mCallbacks.getBroadcastItem(i);
+ try {
+ invokeCallback(callback, msg.what, args);
+ } catch (RemoteException ignored) {
+ }
+ }
+ mCallbacks.finishBroadcast();
+ args.recycle();
+ }
+
+ private void invokeCallback(IStorageEventListener callback, int what, SomeArgs args)
+ throws RemoteException {
+ switch (what) {
+ case MSG_STORAGE_STATE_CHANGED: {
+ callback.onStorageStateChanged((String) args.arg1, (String) args.arg2,
+ (String) args.arg3);
+ break;
+ }
+ case MSG_VOLUME_STATE_CHANGED: {
+ callback.onVolumeStateChanged((VolumeInfo) args.arg1, args.argi2, args.argi3);
+ break;
+ }
+ case MSG_VOLUME_RECORD_CHANGED: {
+ callback.onVolumeRecordChanged((VolumeRecord) args.arg1);
+ break;
+ }
+ case MSG_VOLUME_FORGOTTEN: {
+ callback.onVolumeForgotten((String) args.arg1);
+ break;
+ }
+ case MSG_DISK_SCANNED: {
+ callback.onDiskScanned((DiskInfo) args.arg1, args.argi2);
+ break;
+ }
+ case MSG_DISK_DESTROYED: {
+ callback.onDiskDestroyed((DiskInfo) args.arg1);
+ break;
+ }
+ }
+ }
+
+ private void notifyStorageStateChanged(String path, String oldState, String newState) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = path;
+ args.arg2 = oldState;
+ args.arg3 = newState;
+ obtainMessage(MSG_STORAGE_STATE_CHANGED, args).sendToTarget();
+ }
+
+ private void notifyVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = vol.clone();
+ args.argi2 = oldState;
+ args.argi3 = newState;
+ obtainMessage(MSG_VOLUME_STATE_CHANGED, args).sendToTarget();
+ }
+
+ private void notifyVolumeRecordChanged(VolumeRecord rec) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = rec.clone();
+ obtainMessage(MSG_VOLUME_RECORD_CHANGED, args).sendToTarget();
+ }
+
+ private void notifyVolumeForgotten(String fsUuid) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = fsUuid;
+ obtainMessage(MSG_VOLUME_FORGOTTEN, args).sendToTarget();
+ }
+
+ private void notifyDiskScanned(DiskInfo disk, int volumeCount) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = disk.clone();
+ args.argi2 = volumeCount;
+ obtainMessage(MSG_DISK_SCANNED, args).sendToTarget();
+ }
+
+ private void notifyDiskDestroyed(DiskInfo disk) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = disk.clone();
+ obtainMessage(MSG_DISK_DESTROYED, args).sendToTarget();
+ }
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return;
+
+ final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ", 160);
+ synchronized (mLock) {
+ pw.println("Disks:");
+ pw.increaseIndent();
+ for (int i = 0; i < mDisks.size(); i++) {
+ final DiskInfo disk = mDisks.valueAt(i);
+ disk.dump(pw);
+ }
+ pw.decreaseIndent();
+
+ pw.println();
+ pw.println("Volumes:");
+ pw.increaseIndent();
+ for (int i = 0; i < mVolumes.size(); i++) {
+ final VolumeInfo vol = mVolumes.valueAt(i);
+ if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.id)) continue;
+ vol.dump(pw);
+ }
+ pw.decreaseIndent();
+
+ pw.println();
+ pw.println("Records:");
+ pw.increaseIndent();
+ for (int i = 0; i < mRecords.size(); i++) {
+ final VolumeRecord note = mRecords.valueAt(i);
+ note.dump(pw);
+ }
+ pw.decreaseIndent();
+
+ pw.println();
+ pw.println("Primary storage UUID: " + mPrimaryStorageUuid);
+
+ pw.println();
+ final Pair<String, Long> pair = StorageManager.getPrimaryStoragePathAndSize();
+ if (pair == null) {
+ pw.println("Internal storage total size: N/A");
+ } else {
+ pw.print("Internal storage (");
+ pw.print(pair.first);
+ pw.print(") total size: ");
+ pw.print(pair.second);
+ pw.print(" (");
+ pw.print(DataUnit.MEBIBYTES.toBytes(pair.second));
+ pw.println(" MiB)");
+ }
+
+ pw.println();
+ pw.println("Local unlocked users: " + mLocalUnlockedUsers);
+ pw.println("System unlocked users: " + Arrays.toString(mSystemUnlockedUsers));
+
+ final ContentResolver cr = mContext.getContentResolver();
+ pw.println();
+ pw.println("Isolated storage, local feature flag: "
+ + Settings.Global.getInt(cr, Settings.Global.ISOLATED_STORAGE_LOCAL, 0));
+ pw.println("Isolated storage, remote feature flag: "
+ + Settings.Global.getInt(cr, Settings.Global.ISOLATED_STORAGE_REMOTE, 0));
+ pw.println("Isolated storage, resolved: " + StorageManager.hasIsolatedStorage());
+ pw.println("Forced scoped storage app list: "
+ + DeviceConfig.getProperty(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+ PROP_FORCED_SCOPED_STORAGE_WHITELIST));
+ pw.println("isAutomotive:" + mIsAutomotive);
+ }
+
+ synchronized (mObbMounts) {
+ pw.println();
+ pw.println("mObbMounts:");
+ pw.increaseIndent();
+ final Iterator<Entry<IBinder, List<ObbState>>> binders = mObbMounts.entrySet()
+ .iterator();
+ while (binders.hasNext()) {
+ Entry<IBinder, List<ObbState>> e = binders.next();
+ pw.println(e.getKey() + ":");
+ pw.increaseIndent();
+ final List<ObbState> obbStates = e.getValue();
+ for (final ObbState obbState : obbStates) {
+ pw.println(obbState);
+ }
+ pw.decreaseIndent();
+ }
+ pw.decreaseIndent();
+
+ pw.println();
+ pw.println("mObbPathToStateMap:");
+ pw.increaseIndent();
+ final Iterator<Entry<String, ObbState>> maps =
+ mObbPathToStateMap.entrySet().iterator();
+ while (maps.hasNext()) {
+ final Entry<String, ObbState> e = maps.next();
+ pw.print(e.getKey());
+ pw.print(" -> ");
+ pw.println(e.getValue());
+ }
+ pw.decreaseIndent();
+ }
+
+ pw.println();
+ pw.print("Last maintenance: ");
+ pw.println(TimeUtils.formatForLogging(mLastMaintenance));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void monitor() {
+ try {
+ mVold.monitor();
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+ }
+
+ private final class StorageManagerInternalImpl extends StorageManagerInternal {
+ // Not guarded by a lock.
+ private final CopyOnWriteArrayList<ExternalStorageMountPolicy> mPolicies =
+ new CopyOnWriteArrayList<>();
+
+ @GuardedBy("mResetListeners")
+ private final List<StorageManagerInternal.ResetListener> mResetListeners =
+ new ArrayList<>();
+
+ @Override
+ public void addExternalStoragePolicy(ExternalStorageMountPolicy policy) {
+ // No locking - CopyOnWriteArrayList
+ mPolicies.add(policy);
+ }
+
+ /**
+ * Check if fuse is running in target user, if it's running then setup its storage dirs.
+ * Return true if storage dirs are mounted.
+ */
+ @Override
+ public boolean prepareStorageDirs(int userId, Set<String> packageList,
+ String processName) {
+ synchronized (mLock) {
+ if (!mFuseMountedUser.contains(userId)) {
+ Slog.w(TAG, "User " + userId + " is not unlocked yet so skip mounting obb");
+ return false;
+ }
+ }
+ try {
+ final IVold vold = IVold.Stub.asInterface(
+ ServiceManager.getServiceOrThrow("vold"));
+ for (String pkg : packageList) {
+ final String packageObbDir =
+ String.format("/storage/emulated/%d/Android/obb/%s/", userId, pkg);
+ final String packageDataDir =
+ String.format("/storage/emulated/%d/Android/data/%s/",
+ userId, pkg);
+
+ // Create package obb and data dir if it doesn't exist.
+ int appUid = UserHandle.getUid(userId, mPmInternal.getPackage(pkg).getUid());
+ File file = new File(packageObbDir);
+ if (!file.exists()) {
+ vold.setupAppDir(packageObbDir, appUid);
+ }
+ file = new File(packageDataDir);
+ if (!file.exists()) {
+ vold.setupAppDir(packageDataDir, appUid);
+ }
+ }
+ } catch (ServiceManager.ServiceNotFoundException | RemoteException e) {
+ Slog.e(TAG, "Unable to create obb and data directories for " + processName,e);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void onExternalStoragePolicyChanged(int uid, String packageName) {
+ final int mountMode = getExternalStorageMountMode(uid, packageName);
+ remountUidExternalStorage(uid, mountMode);
+ }
+
+ @Override
+ public int getExternalStorageMountMode(int uid, String packageName) {
+ if (ENABLE_ISOLATED_STORAGE) {
+ return getMountMode(uid, packageName);
+ }
+ try {
+ if (packageName == null) {
+ final String[] packagesForUid = mIPackageManager.getPackagesForUid(uid);
+ packageName = packagesForUid[0];
+ }
+ } catch (RemoteException e) {
+ // Should not happen - same process
+ }
+ // No locking - CopyOnWriteArrayList
+ int mountMode = Integer.MAX_VALUE;
+ for (ExternalStorageMountPolicy policy : mPolicies) {
+ final int policyMode = policy.getMountMode(uid, packageName);
+ if (policyMode == Zygote.MOUNT_EXTERNAL_NONE) {
+ return Zygote.MOUNT_EXTERNAL_NONE;
+ }
+ mountMode = Math.min(mountMode, policyMode);
+ }
+ if (mountMode == Integer.MAX_VALUE) {
+ return Zygote.MOUNT_EXTERNAL_NONE;
+ }
+ return mountMode;
+ }
+
+ @Override
+ public void addResetListener(StorageManagerInternal.ResetListener listener) {
+ synchronized (mResetListeners) {
+ mResetListeners.add(listener);
+ }
+ }
+
+ public void onReset(IVold vold) {
+ synchronized (mResetListeners) {
+ for (StorageManagerInternal.ResetListener listener : mResetListeners) {
+ listener.onReset(vold);
+ }
+ }
+ }
+
+ @Override
+ public void resetUser(int userId) {
+ // TODO(b/145931219): ideally, we only reset storage for the user in question,
+ // but for now, reset everything.
+ mHandler.obtainMessage(H_RESET).sendToTarget();
+ }
+
+ @Override
+ public boolean hasLegacyExternalStorage(int uid) {
+ synchronized (mLock) {
+ return mUidsWithLegacyExternalStorage.contains(uid);
+ }
+ }
+
+ @Override
+ public void prepareAppDataAfterInstall(String packageName, int uid) {
+ int userId = UserHandle.getUserId(uid);
+ final Environment.UserEnvironment userEnv = new Environment.UserEnvironment(userId);
+
+ // The installer may have downloaded OBBs for this newly installed application;
+ // make sure the OBB dir for the application is setup correctly, if it exists.
+ File[] packageObbDirs = userEnv.buildExternalStorageAppObbDirs(packageName);
+ for (File packageObbDir : packageObbDirs) {
+ try {
+ mVold.fixupAppDir(packageObbDir.getCanonicalPath() + "/", uid);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to get canonical path for " + packageName);
+ } catch (RemoteException | ServiceSpecificException e) {
+ // TODO(b/149975102) there is a known case where this fails, when a new
+ // user is setup and we try to fixup app dirs for some existing apps.
+ // For now catch the exception and don't crash.
+ Log.e(TAG, "Failed to fixup app dir for " + packageName, e);
+ }
+ }
+ }
+
+ @Override
+ public boolean isExternalStorageService(int uid) {
+ return mMediaStoreAuthorityAppId == UserHandle.getAppId(uid);
+ }
+
+ public boolean hasExternalStorage(int uid, String packageName) {
+ // No need to check for system uid. This avoids a deadlock between
+ // PackageManagerService and AppOpsService.
+ if (uid == Process.SYSTEM_UID) {
+ return true;
+ }
+ if (ENABLE_ISOLATED_STORAGE) {
+ return getMountMode(uid, packageName) != Zygote.MOUNT_EXTERNAL_NONE;
+ }
+ // No locking - CopyOnWriteArrayList
+ for (ExternalStorageMountPolicy policy : mPolicies) {
+ final boolean policyHasStorage = policy.hasExternalStorage(uid, packageName);
+ if (!policyHasStorage) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void killAppForOpChange(int code, int uid) {
+ final IActivityManager am = ActivityManager.getService();
+ try {
+ am.killUid(UserHandle.getAppId(uid), UserHandle.USER_ALL,
+ AppOpsManager.opToName(code) + " changed.");
+ } catch (RemoteException e) {
+ }
+ }
+
+ public void onAppOpsChanged(int code, int uid, @Nullable String packageName, int mode) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (mIsFuseEnabled) {
+ // When using FUSE, we may need to kill the app if the op changes
+ switch(code) {
+ case OP_REQUEST_INSTALL_PACKAGES:
+ // Always kill regardless of op change, to remount apps /storage
+ killAppForOpChange(code, uid);
+ return;
+ case OP_MANAGE_EXTERNAL_STORAGE:
+ if (mode != MODE_ALLOWED) {
+ // Only kill if op is denied, to lose external_storage gid
+ // Killing when op is granted to pickup the gid automatically,
+ // results in a bad UX, especially since the gid only gives access
+ // to unreliable volumes, USB OTGs that are rarely mounted. The app
+ // will get the external_storage gid on next organic restart.
+ killAppForOpChange(code, uid);
+ }
+ return;
+ case OP_LEGACY_STORAGE:
+ updateLegacyStorageApps(packageName, uid, mode == MODE_ALLOWED);
+ return;
+ }
+ }
+
+ if (mode == MODE_ALLOWED && (code == OP_READ_EXTERNAL_STORAGE
+ || code == OP_WRITE_EXTERNAL_STORAGE
+ || code == OP_REQUEST_INSTALL_PACKAGES)) {
+ final UserManagerInternal userManagerInternal =
+ LocalServices.getService(UserManagerInternal.class);
+ if (userManagerInternal.isUserInitialized(UserHandle.getUserId(uid))) {
+ onExternalStoragePolicyChanged(uid, packageName);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/lights/LightsService.java b/services/core/java/com/android/server/lights/LightsService.java
index 16e42d21224..f0d7aa6abe7 100644
--- a/services/core/java/com/android/server/lights/LightsService.java
+++ b/services/core/java/com/android/server/lights/LightsService.java
@@ -32,6 +32,7 @@ import android.os.Looper;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemProperties;
import android.os.Trace;
import android.provider.Settings;
import android.util.Slog;
@@ -301,12 +302,13 @@ public class LightsService extends SystemService {
+ ": brightness=" + brightness);
return;
}
+
// Ideally, we'd like to set the brightness mode through the SF/HWC as well, but
// right now we just fall back to the old path through Lights brightessMode is
// anything but USER or the device shouldBeInLowPersistenceMode().
if (brightnessMode == BRIGHTNESS_MODE_USER && !shouldBeInLowPersistenceMode()
&& mHwLight.type == LightsManager.LIGHT_ID_BACKLIGHT
- && mSurfaceControlMaximumBrightness == 255) {
+ && mSurfaceControlMaximumBrightness == 255 && mHwLight.id == 0) {
// New system
// TODO: the last check should be mSurfaceControlMaximumBrightness != 0; the
// reason we enforce 255 right now is to stay consistent with the old path. In
@@ -321,6 +323,51 @@ public class LightsService extends SystemService {
// Old system
int brightnessInt = BrightnessSynchronizer.brightnessFloatToInt(
getContext(), brightness);
+
+ if(mHwLight.id == 0) {
+ String fp = SystemProperties.get("ro.vendor.build.fingerprint", "hello");
+ if(fp.matches(".*astarqlte.*")) {
+ int newBrightness = brightnessInt;
+ if(SystemProperties.getBoolean("persist.sys.samsung.full_brightness", false)) {
+ newBrightness = (int) (brightnessInt * 365.0 / 255.0);
+ }
+ setLightLocked(newBrightness, LIGHT_FLASH_HARDWARE, 0, 0, brightnessMode);
+ return;
+ }
+
+ int useSamsungBacklight = SystemProperties.getInt("persist.sys.phh.samsung_backlight", -1);
+ if(useSamsungBacklight != 0) {
+ if(useSamsungBacklight > 0 ||
+ fp.matches(".*beyond.*lte.*") ||
+ fp.matches(".*(crown|star)[q2]*lte.*") ||
+ fp.matches(".*(SC-0[23]K|SCV3[89]).*")) {
+ int ratio = 100;
+ if(useSamsungBacklight > 1)
+ ratio = useSamsungBacklight;
+ int newBrightness = brightnessInt * ratio;
+ if(SystemProperties.getBoolean("persist.sys.samsung.full_brightness", false)) {
+ newBrightness = (int) (brightnessInt * 40960.0 / 255.0);
+ }
+ setLightLocked(newBrightness, LIGHT_FLASH_HARDWARE, 0, 0, brightnessMode);
+ return;
+ }
+ }
+
+ boolean qcomExtendBrightness = SystemProperties.getBoolean("persist.extend.brightness", false);
+ int scale = SystemProperties.getInt("persist.display.max_brightness", 1023);
+ //This is set by vndk-detect
+ int qcomScale = SystemProperties.getInt("persist.sys.qcom-brightness", -1);
+ if(qcomScale != -1) {
+ qcomExtendBrightness = true;
+ scale = qcomScale;
+ }
+
+ if(qcomExtendBrightness) {
+ setLightLocked(brightnessInt * scale / 255, LIGHT_FLASH_NONE, 0, 0, brightnessMode);
+ return;
+ }
+ }
+
int color = brightnessInt & 0x000000ff;
color = 0xff000000 | (color << 16) | (color << 8) | color;
setLightLocked(color, LIGHT_FLASH_NONE, 0, 0, brightnessMode);
--
2.25.1