diff options
5 files changed, 219 insertions, 25 deletions
diff --git a/cmds/svc/src/com/android/commands/svc/UsbCommand.java b/cmds/svc/src/com/android/commands/svc/UsbCommand.java index adbe9d015626..34f6d7de0cc9 100644 --- a/cmds/svc/src/com/android/commands/svc/UsbCommand.java +++ b/cmds/svc/src/com/android/commands/svc/UsbCommand.java @@ -18,6 +18,7 @@ package com.android.commands.svc; import android.content.Context; import android.hardware.usb.IUsbManager; +import android.hardware.usb.UsbManager; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; @@ -38,6 +39,9 @@ public class UsbCommand extends Svc.Command { + "\n" + "usage: svc usb setFunction [function] [usbDataUnlocked=false]\n" + " Set the current usb function and optionally the data lock state.\n\n" + + " svc usb setScreenUnlockedFunctions [function]\n" + + " Sets the functions which, if the device was charging," + + " become current on screen unlock.\n" + " svc usb getFunction\n" + " Gets the list of currently enabled functions\n"; } @@ -62,6 +66,16 @@ public class UsbCommand extends Svc.Command { } else if ("getFunction".equals(args[1])) { System.err.println(SystemProperties.get("sys.usb.config")); return; + } else if ("setScreenUnlockedFunctions".equals(args[1])) { + IUsbManager usbMgr = IUsbManager.Stub.asInterface(ServiceManager.getService( + Context.USB_SERVICE)); + try { + usbMgr.setScreenUnlockedFunctions((args.length >= 3 ? args[2] : + UsbManager.USB_FUNCTION_NONE)); + } catch (RemoteException e) { + System.err.println("Error communicating with UsbManager: " + e); + } + return; } } System.err.println(longHelp()); diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl index 151e62de7b70..398dda174ba1 100644 --- a/core/java/android/hardware/usb/IUsbManager.aidl +++ b/core/java/android/hardware/usb/IUsbManager.aidl @@ -96,6 +96,11 @@ interface IUsbManager */ void setCurrentFunction(String function, boolean usbDataUnlocked); + /* Sets the screen unlocked USB function(s), which will be set automatically + * when the screen is unlocked. + */ + void setScreenUnlockedFunctions(String function); + /* Allow USB debugging from the attached host. If alwaysAllow is true, add the * the public key to list of host keys that the user has approved. */ diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java index bdb90bcca4f8..7617c2bd196f 100644 --- a/core/java/android/hardware/usb/UsbManager.java +++ b/core/java/android/hardware/usb/UsbManager.java @@ -601,6 +601,32 @@ public class UsbManager { } /** + * Sets the screen unlocked functions, which are persisted and set as the current functions + * whenever the screen is unlocked. + * <p> + * The allowed values are: {@link #USB_FUNCTION_NONE}, + * {@link #USB_FUNCTION_MIDI}, {@link #USB_FUNCTION_MTP}, {@link #USB_FUNCTION_PTP}, + * or {@link #USB_FUNCTION_RNDIS}. + * {@link #USB_FUNCTION_NONE} has the effect of switching off this feature, so functions + * no longer change on screen unlock. + * </p><p> + * Note: When the screen is on, this method will apply given functions as current functions, + * which is asynchronous and may fail silently without applying the requested changes. + * </p> + * + * @param function function to set as default + * + * {@hide} + */ + public void setScreenUnlockedFunctions(String function) { + try { + mService.setScreenUnlockedFunctions(function); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Returns a list of physical USB ports on the device. * <p> * This list is guaranteed to contain all dual-role USB Type C ports but it might diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index 1b057f9b9681..4a7072d03067 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -16,6 +16,9 @@ package com.android.server.usb; +import android.app.ActivityManager; +import android.app.ActivityManagerInternal; +import android.app.KeyguardManager; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; @@ -26,6 +29,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.Resources; import android.database.ContentObserver; @@ -38,6 +42,7 @@ import android.hardware.usb.UsbManager; import android.hardware.usb.UsbPort; import android.hardware.usb.UsbPortStatus; import android.os.BatteryManager; +import android.os.Environment; import android.os.FileUtils; import android.os.Handler; import android.os.Looper; @@ -60,6 +65,7 @@ import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.SomeArgs; import com.android.internal.util.IndentingPrintWriter; import com.android.server.FgThread; +import com.android.server.LocalServices; import java.io.File; import java.io.FileNotFoundException; @@ -75,7 +81,7 @@ import java.util.Set; /** * UsbDeviceManager manages USB state in device mode. */ -public class UsbDeviceManager { +public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver { private static final String TAG = "UsbDeviceManager"; private static final boolean DEBUG = false; @@ -97,6 +103,12 @@ public class UsbDeviceManager { private static final String USB_STATE_PROPERTY = "sys.usb.state"; /** + * The SharedPreference setting per user that stores the screen unlocked functions between + * sessions. + */ + private static final String UNLOCKED_CONFIG_PREF = "usb-screen-unlocked-config-%d"; + + /** * ro.bootmode value when phone boots into usual Android. */ private static final String NORMAL_BOOT = "normal"; @@ -128,6 +140,8 @@ public class UsbDeviceManager { private static final int MSG_UPDATE_CHARGING_STATE = 9; private static final int MSG_UPDATE_HOST_STATE = 10; private static final int MSG_LOCALE_CHANGED = 11; + private static final int MSG_SET_SCREEN_UNLOCKED_FUNCTIONS = 12; + private static final int MSG_UPDATE_SCREEN_LOCK = 13; private static final int AUDIO_MODE_SOURCE = 1; @@ -169,6 +183,7 @@ public class UsbDeviceManager { private Intent mBroadcastedIntent; private boolean mPendingBootBroadcast; private static Set<Integer> sBlackListedInterfaces; + private SharedPreferences mSettings; static { sBlackListedInterfaces = new HashSet<>(); @@ -217,6 +232,31 @@ public class UsbDeviceManager { } }; + @Override + public void onKeyguardStateChanged(boolean isShowing) { + int userHandle = ActivityManager.getCurrentUser(); + boolean secure = mContext.getSystemService(KeyguardManager.class) + .isDeviceSecure(userHandle); + boolean unlocking = mContext.getSystemService(UserManager.class) + .isUserUnlockingOrUnlocked(userHandle); + if (DEBUG) { + Slog.v(TAG, "onKeyguardStateChanged: isShowing:" + isShowing + " secure:" + secure + + " unlocking:" + unlocking + " user:" + userHandle); + } + // We are unlocked when the keyguard is down or non-secure, and user storage is unlocked. + mHandler.sendMessage(MSG_UPDATE_SCREEN_LOCK, (isShowing && secure) || !unlocking); + } + + @Override + public void onAwakeStateChanged(boolean isAwake) { + // ignore + } + + /** Called when a user is unlocked. */ + public void onUnlockUser(int userHandle) { + onKeyguardStateChanged(false); + } + public UsbDeviceManager(Context context, UsbAlsaManager alsaManager, UsbSettingsManager settingsManager) { mContext = context; @@ -303,6 +343,8 @@ public class UsbDeviceManager { public void systemReady() { if (DEBUG) Slog.d(TAG, "systemReady"); + LocalServices.getService(ActivityManagerInternal.class).registerScreenObserver(this); + mNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); @@ -407,6 +449,14 @@ public class UsbDeviceManager { return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK); } + private SharedPreferences getPinnedSharedPrefs(Context context) { + final File prefsFile = new File(new File( + Environment.getDataUserCePackageDirectory(StorageManager.UUID_PRIVATE_INTERNAL, + context.getUserId(), context.getPackageName()), "shared_prefs"), + UsbDeviceManager.class.getSimpleName() + ".xml"); + return context.getSharedPreferences(prefsFile, Context.MODE_PRIVATE); + } + private final class UsbHandler extends Handler { // current USB state @@ -423,11 +473,13 @@ public class UsbDeviceManager { private UsbAccessory mCurrentAccessory; private int mUsbNotificationId; private boolean mAdbNotificationShown; - private int mCurrentUser = UserHandle.USER_NULL; + private int mCurrentUser; private boolean mUsbCharging; private String mCurrentOemFunctions; private boolean mHideUsbNotification; private boolean mSupportsAllCombinations; + private String mScreenUnlockedFunctions = UsbManager.USB_FUNCTION_NONE; + private boolean mScreenLocked; public UsbHandler(Looper looper) { super(looper); @@ -449,6 +501,9 @@ public class UsbDeviceManager { SystemProperties.get(USB_STATE_PROPERTY)); } + mCurrentUser = ActivityManager.getCurrentUser(); + mScreenLocked = true; + /* * Use the normal bootmode persistent prop to maintain state of adb across * all boot modes. @@ -653,7 +708,7 @@ public class UsbDeviceManager { private boolean trySetEnabledFunctions(String functions, boolean forceRestart) { if (functions == null || applyAdbFunction(functions) .equals(UsbManager.USB_FUNCTION_NONE)) { - functions = getDefaultFunctions(); + functions = getChargingFunctions(); } functions = applyAdbFunction(functions); @@ -876,6 +931,14 @@ public class UsbDeviceManager { mMidiEnabled && mConfigured, mMidiCard, mMidiDevice); } + private void setScreenUnlockedFunctions() { + setEnabledFunctions(mScreenUnlockedFunctions, false, + UsbManager.containsFunction(mScreenUnlockedFunctions, + UsbManager.USB_FUNCTION_MTP) + || UsbManager.containsFunction(mScreenUnlockedFunctions, + UsbManager.USB_FUNCTION_PTP)); + } + @Override public void handleMessage(Message msg) { switch (msg.what) { @@ -895,7 +958,13 @@ public class UsbDeviceManager { if (mBootCompleted) { if (!mConnected && !hasMessages(MSG_ACCESSORY_MODE_ENTER_TIMEOUT)) { // restore defaults when USB is disconnected - setEnabledFunctions(null, !mAdbEnabled, false); + if (!mScreenLocked + && !UsbManager.USB_FUNCTION_NONE.equals( + mScreenUnlockedFunctions)) { + setScreenUnlockedFunctions(); + } else { + setEnabledFunctions(null, !mAdbEnabled, false); + } } updateUsbFunctions(); } else { @@ -978,6 +1047,47 @@ public class UsbDeviceManager { String functions = (String) msg.obj; setEnabledFunctions(functions, false, msg.arg1 == 1); break; + case MSG_SET_SCREEN_UNLOCKED_FUNCTIONS: + mScreenUnlockedFunctions = (String) msg.obj; + SharedPreferences.Editor editor = mSettings.edit(); + editor.putString(String.format(Locale.ENGLISH, UNLOCKED_CONFIG_PREF, + mCurrentUser), mScreenUnlockedFunctions); + editor.commit(); + if (!mScreenLocked && !UsbManager.USB_FUNCTION_NONE.equals( + mScreenUnlockedFunctions)) { + // If the screen is unlocked, also set current functions. + setScreenUnlockedFunctions(); + } + break; + case MSG_UPDATE_SCREEN_LOCK: + if (msg.arg1 == 1 == mScreenLocked) { + break; + } + mScreenLocked = msg.arg1 == 1; + if (mSettings == null && !mScreenLocked) { + // Shared preferences aren't accessible until the user has been unlocked. + mSettings = getPinnedSharedPrefs(mContext); + mScreenUnlockedFunctions = mSettings.getString( + String.format(Locale.ENGLISH, UNLOCKED_CONFIG_PREF, mCurrentUser), + UsbManager.USB_FUNCTION_NONE); + } + if (!mBootCompleted) { + break; + } + if (mScreenLocked) { + if (!mConnected) { + setEnabledFunctions(null, false, false); + } + } else { + if (!UsbManager.USB_FUNCTION_NONE.equals(mScreenUnlockedFunctions) + && (UsbManager.USB_FUNCTION_ADB.equals(mCurrentFunctions) + || (UsbManager.USB_FUNCTION_MTP.equals(mCurrentFunctions) + && !mUsbDataUnlocked))) { + // Set the screen unlocked functions if current function is charging. + setScreenUnlockedFunctions(); + } + } + break; case MSG_UPDATE_USER_RESTRICTIONS: // Restart the USB stack if USB transfer is enabled but no longer allowed. final boolean forceRestart = mUsbDataUnlocked @@ -1001,7 +1111,13 @@ public class UsbDeviceManager { updateUsbStateBroadcastIfNeeded(false); mPendingBootBroadcast = false; } - setEnabledFunctions(null, false, false); + + if (!mScreenLocked + && !UsbManager.USB_FUNCTION_NONE.equals(mScreenUnlockedFunctions)) { + setScreenUnlockedFunctions(); + } else { + setEnabledFunctions(null, false, false); + } if (mCurrentAccessory != null) { getCurrentSettings().accessoryAttached(mCurrentAccessory); } @@ -1011,16 +1127,15 @@ public class UsbDeviceManager { break; case MSG_USER_SWITCHED: { if (mCurrentUser != msg.arg1) { - // Restart the USB stack and re-apply user restrictions for MTP or PTP. - if (mUsbDataUnlocked - && isUsbDataTransferActive() - && mCurrentUser != UserHandle.USER_NULL) { - Slog.v(TAG, "Current user switched to " + msg.arg1 - + "; resetting USB host stack for MTP or PTP"); - // avoid leaking sensitive data from previous user - setEnabledFunctions(null, true, false); + if (DEBUG) { + Slog.v(TAG, "Current user switched to " + msg.arg1); } mCurrentUser = msg.arg1; + mScreenLocked = true; + mScreenUnlockedFunctions = mSettings.getString( + String.format(Locale.ENGLISH, UNLOCKED_CONFIG_PREF, mCurrentUser), + UsbManager.USB_FUNCTION_NONE); + setEnabledFunctions(null, false, false); } break; } @@ -1072,20 +1187,12 @@ public class UsbDeviceManager { titleRes = com.android.internal.R.string.usb_unsupported_audio_accessory_title; id = SystemMessage.NOTE_USB_AUDIO_ACCESSORY_NOT_SUPPORTED; } else if (mConnected) { - if (!mUsbDataUnlocked) { - if (mSourcePower) { - titleRes = com.android.internal.R.string.usb_supplying_notification_title; - id = SystemMessage.NOTE_USB_SUPPLYING; - } else { - titleRes = com.android.internal.R.string.usb_charging_notification_title; - id = SystemMessage.NOTE_USB_CHARGING; - } - } else if (UsbManager.containsFunction(mCurrentFunctions, - UsbManager.USB_FUNCTION_MTP)) { + if (UsbManager.containsFunction(mCurrentFunctions, + UsbManager.USB_FUNCTION_MTP) && mUsbDataUnlocked) { titleRes = com.android.internal.R.string.usb_mtp_notification_title; id = SystemMessage.NOTE_USB_MTP; } else if (UsbManager.containsFunction(mCurrentFunctions, - UsbManager.USB_FUNCTION_PTP)) { + UsbManager.USB_FUNCTION_PTP) && mUsbDataUnlocked) { titleRes = com.android.internal.R.string.usb_ptp_notification_title; id = SystemMessage.NOTE_USB_PTP; } else if (UsbManager.containsFunction(mCurrentFunctions, @@ -1236,7 +1343,7 @@ public class UsbDeviceManager { } } - private String getDefaultFunctions() { + private String getChargingFunctions() { String func = SystemProperties.get(getPersistProp(true), UsbManager.USB_FUNCTION_NONE); // if ADB is enabled, reset functions to ADB @@ -1253,6 +1360,8 @@ public class UsbDeviceManager { pw.println(" mCurrentFunctions: " + mCurrentFunctions); pw.println(" mCurrentOemFunctions: " + mCurrentOemFunctions); pw.println(" mCurrentFunctionsApplied: " + mCurrentFunctionsApplied); + pw.println(" mScreenUnlockedFunctions: " + mScreenUnlockedFunctions); + pw.println(" mScreenLocked: " + mScreenLocked); pw.println(" mConnected: " + mConnected); pw.println(" mConfigured: " + mConfigured); pw.println(" mUsbDataUnlocked: " + mUsbDataUnlocked); @@ -1309,6 +1418,17 @@ public class UsbDeviceManager { mHandler.sendMessage(MSG_SET_CURRENT_FUNCTIONS, functions, usbDataUnlocked); } + /** + * Sets the functions which are set when the screen is unlocked. + * @param functions Functions to set. + */ + public void setScreenUnlockedFunctions(String functions) { + if (DEBUG) { + Slog.d(TAG, "setScreenUnlockedFunctions(" + functions + ")"); + } + mHandler.sendMessage(MSG_SET_SCREEN_UNLOCKED_FUNCTIONS, functions); + } + private void readOemUsbOverrideConfig() { String[] configList = mContext.getResources().getStringArray( com.android.internal.R.array.config_oemUsbModeOverride); diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java index 8554cf7b186f..1a20819b9a80 100644 --- a/services/usb/java/com/android/server/usb/UsbService.java +++ b/services/usb/java/com/android/server/usb/UsbService.java @@ -87,6 +87,11 @@ public class UsbService extends IUsbManager.Stub { public void onStopUser(int userHandle) { mUsbService.onStopUser(UserHandle.of(userHandle)); } + + @Override + public void onUnlockUser(int userHandle) { + mUsbService.onUnlockUser(userHandle); + } } private static final String TAG = "UsbService"; @@ -205,6 +210,13 @@ public class UsbService extends IUsbManager.Stub { } } + /** Called when a user is unlocked. */ + public void onUnlockUser(int user) { + if (mDeviceManager != null) { + mDeviceManager.onUnlockUser(user); + } + } + /* Returns a list of all currently attached USB devices (host mdoe) */ @Override public void getDeviceList(Bundle devices) { @@ -392,6 +404,23 @@ public class UsbService extends IUsbManager.Stub { } } + @Override + public void setScreenUnlockedFunctions(String function) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); + + if (!isSupportedCurrentFunction(function)) { + Slog.w(TAG, "Caller of setScreenUnlockedFunctions() requested unsupported USB function:" + + function); + function = UsbManager.USB_FUNCTION_NONE; + } + + if (mDeviceManager != null) { + mDeviceManager.setScreenUnlockedFunctions(function); + } else { + throw new IllegalStateException("USB device mode not supported"); + } + } + private static boolean isSupportedCurrentFunction(String function) { if (function == null) return true; |