1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.providers.media; 18 19 import android.app.ActivityManager; 20 import android.app.KeyguardManager; 21 import android.app.Service; 22 import android.content.BroadcastReceiver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.IntentFilter; 26 import android.hardware.usb.UsbManager; 27 import android.mtp.MtpDatabase; 28 import android.mtp.MtpServer; 29 import android.mtp.MtpStorage; 30 import android.os.Environment; 31 import android.os.IBinder; 32 import android.os.UserHandle; 33 import android.os.storage.StorageEventListener; 34 import android.os.storage.StorageManager; 35 import android.os.storage.StorageVolume; 36 import android.util.Log; 37 38 import java.io.File; 39 import java.util.HashMap; 40 41 public class MtpService extends Service { 42 private static final String TAG = "MtpService"; 43 private static final boolean LOGD = true; 44 45 // We restrict PTP to these subdirectories 46 private static final String[] PTP_DIRECTORIES = new String[] { 47 Environment.DIRECTORY_DCIM, 48 Environment.DIRECTORY_PICTURES, 49 }; 50 addStorageDevicesLocked()51 private void addStorageDevicesLocked() { 52 if (mPtpMode) { 53 // In PTP mode we support only primary storage 54 final StorageVolume primary = StorageManager.getPrimaryVolume(mVolumes); 55 final String path = primary.getPath(); 56 if (path != null) { 57 String state = mStorageManager.getVolumeState(path); 58 if (Environment.MEDIA_MOUNTED.equals(state)) { 59 addStorageLocked(mVolumeMap.get(path)); 60 } 61 } 62 } else { 63 for (StorageVolume volume : mVolumeMap.values()) { 64 addStorageLocked(volume); 65 } 66 } 67 } 68 69 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 70 @Override 71 public void onReceive(Context context, Intent intent) { 72 final String action = intent.getAction(); 73 if (Intent.ACTION_USER_PRESENT.equals(action)) { 74 // If the media scanner is running, it may currently be calling 75 // sendObjectAdded/Removed, which also synchronizes on mBinder 76 // (and in addition to that, all the native MtpServer methods 77 // lock the same Mutex). If it happens to be in an mtp device 78 // write(), it may block for some time, so process this broadcast 79 // in a thread. 80 new Thread(new Runnable() { 81 @Override 82 public void run() { 83 synchronized (mBinder) { 84 // Unhide the storage units when the user has unlocked the lockscreen 85 if (mMtpDisabled) { 86 addStorageDevicesLocked(); 87 mMtpDisabled = false; 88 } 89 } 90 }}, "addStorageDevices").start(); 91 } 92 } 93 }; 94 95 private final StorageEventListener mStorageEventListener = new StorageEventListener() { 96 @Override 97 public void onStorageStateChanged(String path, String oldState, String newState) { 98 synchronized (mBinder) { 99 Log.d(TAG, "onStorageStateChanged " + path + " " + oldState + " -> " + newState); 100 if (Environment.MEDIA_MOUNTED.equals(newState)) { 101 volumeMountedLocked(path); 102 } else if (Environment.MEDIA_MOUNTED.equals(oldState)) { 103 StorageVolume volume = mVolumeMap.remove(path); 104 if (volume != null) { 105 removeStorageLocked(volume); 106 } 107 } 108 } 109 } 110 }; 111 112 private MtpDatabase mDatabase; 113 private MtpServer mServer; 114 private StorageManager mStorageManager; 115 /** Flag indicating if MTP is disabled due to keyguard */ 116 private boolean mMtpDisabled; 117 private boolean mPtpMode; 118 private final HashMap<String, StorageVolume> mVolumeMap = new HashMap<String, StorageVolume>(); 119 private final HashMap<String, MtpStorage> mStorageMap = new HashMap<String, MtpStorage>(); 120 private StorageVolume[] mVolumes; 121 122 @Override onCreate()123 public void onCreate() { 124 registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_USER_PRESENT)); 125 126 mStorageManager = StorageManager.from(this); 127 synchronized (mBinder) { 128 updateDisabledStateLocked(); 129 mStorageManager.registerListener(mStorageEventListener); 130 StorageVolume[] volumes = mStorageManager.getVolumeList(); 131 mVolumes = volumes; 132 for (int i = 0; i < volumes.length; i++) { 133 String path = volumes[i].getPath(); 134 String state = mStorageManager.getVolumeState(path); 135 if (Environment.MEDIA_MOUNTED.equals(state)) { 136 volumeMountedLocked(path); 137 } 138 } 139 } 140 } 141 142 @Override onStartCommand(Intent intent, int flags, int startId)143 public int onStartCommand(Intent intent, int flags, int startId) { 144 synchronized (mBinder) { 145 updateDisabledStateLocked(); 146 mPtpMode = (intent == null ? false 147 : intent.getBooleanExtra(UsbManager.USB_FUNCTION_PTP, false)); 148 String[] subdirs = null; 149 if (mPtpMode) { 150 int count = PTP_DIRECTORIES.length; 151 subdirs = new String[count]; 152 for (int i = 0; i < count; i++) { 153 File file = 154 Environment.getExternalStoragePublicDirectory(PTP_DIRECTORIES[i]); 155 // make sure this directory exists 156 file.mkdirs(); 157 subdirs[i] = file.getPath(); 158 } 159 } 160 final StorageVolume primary = StorageManager.getPrimaryVolume(mVolumes); 161 if (mDatabase != null) { 162 mDatabase.setServer(null); 163 } 164 mDatabase = new MtpDatabase(this, MediaProvider.EXTERNAL_VOLUME, 165 primary.getPath(), subdirs); 166 manageServiceLocked(); 167 } 168 169 return START_STICKY; 170 } 171 updateDisabledStateLocked()172 private void updateDisabledStateLocked() { 173 final boolean isCurrentUser = UserHandle.myUserId() == ActivityManager.getCurrentUser(); 174 final KeyguardManager keyguardManager = (KeyguardManager) getSystemService( 175 Context.KEYGUARD_SERVICE); 176 mMtpDisabled = (keyguardManager.isKeyguardLocked() && keyguardManager.isKeyguardSecure()) 177 || !isCurrentUser; 178 if (LOGD) { 179 Log.d(TAG, "updating state; isCurrentUser=" + isCurrentUser + ", mMtpLocked=" 180 + mMtpDisabled); 181 } 182 } 183 184 /** 185 * Manage {@link #mServer}, creating only when running as the current user. 186 */ manageServiceLocked()187 private void manageServiceLocked() { 188 final boolean isCurrentUser = UserHandle.myUserId() == ActivityManager.getCurrentUser(); 189 if (mServer == null && isCurrentUser) { 190 Log.d(TAG, "starting MTP server in " + (mPtpMode ? "PTP mode" : "MTP mode")); 191 mServer = new MtpServer(mDatabase, mPtpMode); 192 mDatabase.setServer(mServer); 193 if (!mMtpDisabled) { 194 addStorageDevicesLocked(); 195 } 196 mServer.start(); 197 } else if (mServer != null && !isCurrentUser) { 198 Log.d(TAG, "no longer current user; shutting down MTP server"); 199 // Internally, kernel will close our FD, and server thread will 200 // handle cleanup. 201 mServer = null; 202 mDatabase.setServer(null); 203 } 204 } 205 206 @Override onDestroy()207 public void onDestroy() { 208 unregisterReceiver(mReceiver); 209 mStorageManager.unregisterListener(mStorageEventListener); 210 if (mDatabase != null) { 211 mDatabase.setServer(null); 212 } 213 } 214 215 private final IMtpService.Stub mBinder = 216 new IMtpService.Stub() { 217 public void sendObjectAdded(int objectHandle) { 218 synchronized (mBinder) { 219 if (mServer != null) { 220 mServer.sendObjectAdded(objectHandle); 221 } 222 } 223 } 224 225 public void sendObjectRemoved(int objectHandle) { 226 synchronized (mBinder) { 227 if (mServer != null) { 228 mServer.sendObjectRemoved(objectHandle); 229 } 230 } 231 } 232 }; 233 234 @Override onBind(Intent intent)235 public IBinder onBind(Intent intent) { 236 return mBinder; 237 } 238 volumeMountedLocked(String path)239 private void volumeMountedLocked(String path) { 240 for (int i = 0; i < mVolumes.length; i++) { 241 StorageVolume volume = mVolumes[i]; 242 if (volume.getPath().equals(path)) { 243 mVolumeMap.put(path, volume); 244 if (!mMtpDisabled) { 245 // In PTP mode we support only primary storage 246 if (volume.isPrimary() || !mPtpMode) { 247 addStorageLocked(volume); 248 } 249 } 250 break; 251 } 252 } 253 } 254 addStorageLocked(StorageVolume volume)255 private void addStorageLocked(StorageVolume volume) { 256 MtpStorage storage = new MtpStorage(volume, getApplicationContext()); 257 String path = storage.getPath(); 258 mStorageMap.put(path, storage); 259 260 Log.d(TAG, "addStorageLocked " + storage.getStorageId() + " " + path); 261 if (mDatabase != null) { 262 mDatabase.addStorage(storage); 263 } 264 if (mServer != null) { 265 mServer.addStorage(storage); 266 } 267 } 268 removeStorageLocked(StorageVolume volume)269 private void removeStorageLocked(StorageVolume volume) { 270 MtpStorage storage = mStorageMap.remove(volume.getPath()); 271 if (storage == null) { 272 Log.e(TAG, "no MtpStorage for " + volume.getPath()); 273 return; 274 } 275 276 Log.d(TAG, "removeStorageLocked " + storage.getStorageId() + " " + storage.getPath()); 277 if (mDatabase != null) { 278 mDatabase.removeStorage(storage); 279 } 280 if (mServer != null) { 281 mServer.removeStorage(storage); 282 } 283 } 284 } 285