8113 lines
326 KiB
Diff
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 è.
|
|
+ */
|
|
+ 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
|
|
|