Multi-user MTP, handle secondary user boot.
Watch for BOOT_COMPLETED, and dispatch any current USB state or
mounted volumes which may have already occured. This specifically
handles starting of secondary users.
The current MTP kernel driver at /dev/mtp_usb is exclusive, meaning
only one process can have it open. To solve this, the framework
cycles the USB host stack when switching users, giving the new
user's media process a chance to claim the kernel device. We only
start an MtpServer when we're the active user.
Bug: 6925114
Change-Id: Idfcda09aed88140bb470a110a9e4434f5b79abdd
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 34b6a94..f035150 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -10,6 +10,8 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_MTP" />
+ <uses-permission android:name="android.permission.MANAGE_USERS" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
<application android:process="android.process.media"
android:label="@string/app_label"
@@ -57,12 +59,15 @@
</intent-filter>
</service>
- <receiver android:name="UsbReceiver">
+ <receiver android:name=".MtpReceiver">
+ <intent-filter>
+ <action android:name="android.intent.action.BOOT_COMPLETED" />
+ </intent-filter>
<intent-filter>
<action android:name="android.hardware.usb.action.USB_STATE" />
</intent-filter>
</receiver>
-
+
<service android:name="MtpService" />
<activity android:name="RingtonePickerActivity"
diff --git a/src/com/android/providers/media/MediaScannerReceiver.java b/src/com/android/providers/media/MediaScannerReceiver.java
index fe53a23..53eec55 100644
--- a/src/com/android/providers/media/MediaScannerReceiver.java
+++ b/src/com/android/providers/media/MediaScannerReceiver.java
@@ -17,28 +17,38 @@
package com.android.providers.media;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.content.BroadcastReceiver;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
+import android.os.storage.StorageManager;
+import android.os.storage.StorageVolume;
import android.util.Log;
-import java.io.File;
-
-
-public class MediaScannerReceiver extends BroadcastReceiver
-{
+public class MediaScannerReceiver extends BroadcastReceiver {
private final static String TAG = "MediaScannerReceiver";
@Override
public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- Uri uri = intent.getData();
- if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
- // scan internal storage
+ final String action = intent.getAction();
+ final Uri uri = intent.getData();
+ if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
+ // Scan internal storage
scan(context, MediaProvider.INTERNAL_VOLUME);
+
+ // Scan any available external storage
+ // TODO: switch to atomic fetching of volume state
+ final StorageManager sm = StorageManager.from(context);
+ for (StorageVolume volume : sm.getVolumeList()) {
+ final String state = sm.getVolumeState(volume.getPath());
+ if (Environment.MEDIA_MOUNTED.equals(state)
+ || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
+ scan(context, volume.getPath());
+ }
+ }
+
} else {
if (uri.getScheme().equals("file")) {
// handle intents related to external storage
@@ -46,10 +56,10 @@
String externalStoragePath = Environment.getExternalStorageDirectory().getPath();
Log.d(TAG, "action: " + action + " path: " + path);
- if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
+ if (Intent.ACTION_MEDIA_MOUNTED.equals(action)) {
// scan whenever any volume is mounted
scan(context, MediaProvider.EXTERNAL_VOLUME);
- } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE) &&
+ } else if (Intent.ACTION_MEDIA_SCANNER_SCAN_FILE.equals(action) &&
path != null && path.startsWith(externalStoragePath + "/")) {
scanFile(context, path);
}
@@ -71,5 +81,3 @@
new Intent(context, MediaScannerService.class).putExtras(args));
}
}
-
-
diff --git a/src/com/android/providers/media/UsbReceiver.java b/src/com/android/providers/media/MtpReceiver.java
similarity index 77%
rename from src/com/android/providers/media/UsbReceiver.java
rename to src/com/android/providers/media/MtpReceiver.java
index 7a39bc8..23d529b 100644
--- a/src/com/android/providers/media/UsbReceiver.java
+++ b/src/com/android/providers/media/MtpReceiver.java
@@ -16,22 +16,32 @@
package com.android.providers.media;
-import android.content.Context;
-import android.content.ContentValues;
-import android.content.Intent;
import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.hardware.usb.UsbManager;
import android.net.Uri;
import android.os.Bundle;
-import android.util.Log;
-
-public class UsbReceiver extends BroadcastReceiver
-{
+public class MtpReceiver extends BroadcastReceiver {
private final static String TAG = "UsbReceiver";
@Override
public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
+ final Intent usbState = context.registerReceiver(
+ null, new IntentFilter(UsbManager.ACTION_USB_STATE));
+ if (usbState != null) {
+ handleUsbState(context, usbState);
+ }
+ } else if (UsbManager.ACTION_USB_STATE.equals(action)) {
+ handleUsbState(context, intent);
+ }
+ }
+
+ private void handleUsbState(Context context, Intent intent) {
Bundle extras = intent.getExtras();
boolean connected = extras.getBoolean(UsbManager.USB_CONFIGURED);
boolean mtpEnabled = extras.getBoolean(UsbManager.USB_FUNCTION_MTP);
@@ -54,5 +64,3 @@
}
}
}
-
-
diff --git a/src/com/android/providers/media/MtpService.java b/src/com/android/providers/media/MtpService.java
index 2d707b9..04033e9 100644
--- a/src/com/android/providers/media/MtpService.java
+++ b/src/com/android/providers/media/MtpService.java
@@ -16,6 +16,7 @@
package com.android.providers.media;
+import android.app.ActivityManager;
import android.app.KeyguardManager;
import android.app.Service;
import android.content.BroadcastReceiver;
@@ -28,6 +29,7 @@
import android.mtp.MtpStorage;
import android.os.Environment;
import android.os.IBinder;
+import android.os.UserHandle;
import android.os.storage.StorageEventListener;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
@@ -38,6 +40,7 @@
public class MtpService extends Service {
private static final String TAG = "MtpService";
+ private static final boolean LOGD = true;
// We restrict PTP to these subdirectories
private static final String[] PTP_DIRECTORIES = new String[] {
@@ -80,6 +83,7 @@
};
private final StorageEventListener mStorageEventListener = new StorageEventListener() {
+ @Override
public void onStorageStateChanged(String path, String oldState, String newState) {
synchronized (mBinder) {
Log.d(TAG, "onStorageStateChanged " + path + " " + oldState + " -> " + newState);
@@ -98,7 +102,8 @@
private MtpDatabase mDatabase;
private MtpServer mServer;
private StorageManager mStorageManager;
- private boolean mMtpDisabled; // true if MTP is disabled due to secure keyguard
+ /** Flag indicating if MTP is disabled due to keyguard */
+ private boolean mMtpDisabled;
private boolean mPtpMode;
private final HashMap<String, StorageVolume> mVolumeMap = new HashMap<String, StorageVolume>();
private final HashMap<String, MtpStorage> mStorageMap = new HashMap<String, MtpStorage>();
@@ -106,14 +111,11 @@
@Override
public void onCreate() {
- // lock MTP if the keyguard is locked and secure
- KeyguardManager keyguardManager =
- (KeyguardManager)getSystemService(Context.KEYGUARD_SERVICE);
- mMtpDisabled = keyguardManager.isKeyguardLocked() && keyguardManager.isKeyguardSecure();
registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_USER_PRESENT));
- mStorageManager = (StorageManager)getSystemService(Context.STORAGE_SERVICE);
+ mStorageManager = StorageManager.from(this);
synchronized (mBinder) {
+ updateDisabledStateLocked();
mStorageManager.registerListener(mStorageEventListener);
StorageVolume[] volumes = mStorageManager.getVolumeList();
mVolumes = volumes;
@@ -121,7 +123,7 @@
String path = volumes[i].getPath();
String state = mStorageManager.getVolumeState(path);
if (Environment.MEDIA_MOUNTED.equals(state)) {
- volumeMountedLocked(path);
+ volumeMountedLocked(path);
}
}
}
@@ -130,39 +132,64 @@
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
synchronized (mBinder) {
- if (mServer == null) {
- mPtpMode = (intent == null ? false
- : intent.getBooleanExtra(UsbManager.USB_FUNCTION_PTP, false));
- Log.d(TAG, "starting MTP server in " + (mPtpMode ? "PTP mode" : "MTP mode"));
- String[] subdirs = null;
- if (mPtpMode) {
- int count = PTP_DIRECTORIES.length;
- subdirs = new String[count];
- for (int i = 0; i < count; i++) {
- File file =
- Environment.getExternalStoragePublicDirectory(PTP_DIRECTORIES[i]);
- // make sure this directory exists
- file.mkdirs();
- subdirs[i] = file.getPath();
- }
+ updateDisabledStateLocked();
+ mPtpMode = (intent == null ? false
+ : intent.getBooleanExtra(UsbManager.USB_FUNCTION_PTP, false));
+ String[] subdirs = null;
+ if (mPtpMode) {
+ int count = PTP_DIRECTORIES.length;
+ subdirs = new String[count];
+ for (int i = 0; i < count; i++) {
+ File file =
+ Environment.getExternalStoragePublicDirectory(PTP_DIRECTORIES[i]);
+ // make sure this directory exists
+ file.mkdirs();
+ subdirs[i] = file.getPath();
}
- final StorageVolume primary = StorageManager.getPrimaryVolume(mVolumes);
- mDatabase = new MtpDatabase(this, MediaProvider.EXTERNAL_VOLUME,
- primary.getPath(), subdirs);
- mServer = new MtpServer(mDatabase, mPtpMode);
- if (!mMtpDisabled) {
- addStorageDevicesLocked();
- }
- mServer.start();
}
+ final StorageVolume primary = StorageManager.getPrimaryVolume(mVolumes);
+ mDatabase = new MtpDatabase(this, MediaProvider.EXTERNAL_VOLUME,
+ primary.getPath(), subdirs);
+ manageServiceLocked();
}
return START_STICKY;
}
+ private void updateDisabledStateLocked() {
+ final boolean isCurrentUser = UserHandle.myUserId() == ActivityManager.getCurrentUser();
+ final KeyguardManager keyguardManager = (KeyguardManager) getSystemService(
+ Context.KEYGUARD_SERVICE);
+ mMtpDisabled = (keyguardManager.isKeyguardLocked() && keyguardManager.isKeyguardSecure())
+ || !isCurrentUser;
+ if (LOGD) {
+ Log.d(TAG, "updating state; isCurrentUser=" + isCurrentUser + ", mMtpLocked="
+ + mMtpDisabled);
+ }
+ }
+
+ /**
+ * Manage {@link #mServer}, creating only when running as the current user.
+ */
+ private void manageServiceLocked() {
+ final boolean isCurrentUser = UserHandle.myUserId() == ActivityManager.getCurrentUser();
+ if (mServer == null && isCurrentUser) {
+ Log.d(TAG, "starting MTP server in " + (mPtpMode ? "PTP mode" : "MTP mode"));
+ mServer = new MtpServer(mDatabase, mPtpMode);
+ if (!mMtpDisabled) {
+ addStorageDevicesLocked();
+ }
+ mServer.start();
+ } else if (mServer != null && !isCurrentUser) {
+ Log.d(TAG, "no longer current user; shutting down MTP server");
+ // Internally, kernel will close our FD, and server thread will
+ // handle cleanup.
+ mServer = null;
+ }
+ }
+
@Override
- public void onDestroy()
- {
+ public void onDestroy() {
unregisterReceiver(mReceiver);
mStorageManager.unregisterListener(mStorageEventListener);
}
@@ -187,8 +214,7 @@
};
@Override
- public IBinder onBind(Intent intent)
- {
+ public IBinder onBind(Intent intent) {
return mBinder;
}
@@ -212,8 +238,8 @@
MtpStorage storage = new MtpStorage(volume, getApplicationContext());
String path = storage.getPath();
mStorageMap.put(path, storage);
-
- Log.d(TAG, "addStorageLocked " + storage.getStorageId() + " " +path);
+
+ Log.d(TAG, "addStorageLocked " + storage.getStorageId() + " " + path);
if (mDatabase != null) {
mDatabase.addStorage(storage);
}
@@ -238,4 +264,3 @@
}
}
}
-