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.Service; 21 import android.content.Intent; 22 import android.hardware.usb.UsbManager; 23 import android.mtp.MtpDatabase; 24 import android.mtp.MtpServer; 25 import android.mtp.MtpStorage; 26 import android.os.Environment; 27 import android.os.IBinder; 28 import android.os.UserHandle; 29 import android.os.storage.StorageEventListener; 30 import android.os.storage.StorageManager; 31 import android.os.storage.StorageVolume; 32 import android.util.Log; 33 34 import java.io.File; 35 import java.util.HashMap; 36 37 public class MtpService extends Service { 38 private static final String TAG = "MtpService"; 39 private static final boolean LOGD = false; 40 41 // We restrict PTP to these subdirectories 42 private static final String[] PTP_DIRECTORIES = new String[] { 43 Environment.DIRECTORY_DCIM, 44 Environment.DIRECTORY_PICTURES, 45 }; 46 addStorageDevicesLocked()47 private void addStorageDevicesLocked() { 48 if (mPtpMode) { 49 // In PTP mode we support only primary storage 50 final StorageVolume primary = StorageManager.getPrimaryVolume(mVolumes); 51 final String path = primary.getPath(); 52 if (path != null) { 53 String state = mStorageManager.getVolumeState(path); 54 if (Environment.MEDIA_MOUNTED.equals(state)) { 55 addStorageLocked(mVolumeMap.get(path)); 56 } 57 } 58 } else { 59 for (StorageVolume volume : mVolumeMap.values()) { 60 addStorageLocked(volume); 61 } 62 } 63 } 64 65 private final StorageEventListener mStorageEventListener = new StorageEventListener() { 66 @Override 67 public void onStorageStateChanged(String path, String oldState, String newState) { 68 synchronized (mBinder) { 69 Log.d(TAG, "onStorageStateChanged " + path + " " + oldState + " -> " + newState); 70 if (Environment.MEDIA_MOUNTED.equals(newState)) { 71 volumeMountedLocked(path); 72 } else if (Environment.MEDIA_MOUNTED.equals(oldState)) { 73 StorageVolume volume = mVolumeMap.remove(path); 74 if (volume != null) { 75 removeStorageLocked(volume); 76 } 77 } 78 } 79 } 80 }; 81 82 private MtpDatabase mDatabase; 83 private MtpServer mServer; 84 private StorageManager mStorageManager; 85 /** Flag indicating if MTP is disabled due to keyguard */ 86 private boolean mMtpDisabled; 87 private boolean mUnlocked; 88 private boolean mPtpMode; 89 private final HashMap<String, StorageVolume> mVolumeMap = new HashMap<String, StorageVolume>(); 90 private final HashMap<String, MtpStorage> mStorageMap = new HashMap<String, MtpStorage>(); 91 private StorageVolume[] mVolumes; 92 93 @Override onCreate()94 public void onCreate() { 95 mStorageManager = StorageManager.from(this); 96 synchronized (mBinder) { 97 updateDisabledStateLocked(); 98 mStorageManager.registerListener(mStorageEventListener); 99 StorageVolume[] volumes = mStorageManager.getVolumeList(); 100 mVolumes = volumes; 101 for (int i = 0; i < volumes.length; i++) { 102 String path = volumes[i].getPath(); 103 String state = mStorageManager.getVolumeState(path); 104 if (Environment.MEDIA_MOUNTED.equals(state)) { 105 volumeMountedLocked(path); 106 } 107 } 108 } 109 } 110 111 @Override onStartCommand(Intent intent, int flags, int startId)112 public int onStartCommand(Intent intent, int flags, int startId) { 113 mUnlocked = intent.getBooleanExtra(UsbManager.USB_DATA_UNLOCKED, false); 114 if (LOGD) { Log.d(TAG, "onStartCommand intent=" + intent + " mUnlocked=" + mUnlocked); } 115 synchronized (mBinder) { 116 updateDisabledStateLocked(); 117 mPtpMode = (intent == null ? false 118 : intent.getBooleanExtra(UsbManager.USB_FUNCTION_PTP, false)); 119 String[] subdirs = null; 120 if (mPtpMode) { 121 int count = PTP_DIRECTORIES.length; 122 subdirs = new String[count]; 123 for (int i = 0; i < count; i++) { 124 File file = 125 Environment.getExternalStoragePublicDirectory(PTP_DIRECTORIES[i]); 126 // make sure this directory exists 127 file.mkdirs(); 128 subdirs[i] = file.getPath(); 129 } 130 } 131 final StorageVolume primary = StorageManager.getPrimaryVolume(mVolumes); 132 if (mDatabase != null) { 133 mDatabase.setServer(null); 134 } 135 manageServiceLocked(primary, subdirs); 136 } 137 138 return START_REDELIVER_INTENT; 139 } 140 updateDisabledStateLocked()141 private void updateDisabledStateLocked() { 142 final boolean isCurrentUser = UserHandle.myUserId() == ActivityManager.getCurrentUser(); 143 mMtpDisabled = !mUnlocked || !isCurrentUser; 144 if (LOGD) { 145 Log.d(TAG, "updating state; isCurrentUser=" + isCurrentUser + ", mMtpLocked=" 146 + mMtpDisabled); 147 } 148 } 149 150 /** 151 * Manage {@link #mServer}, creating only when running as the current user. 152 */ manageServiceLocked(StorageVolume primary, String[] subdirs)153 private void manageServiceLocked(StorageVolume primary, String[] subdirs) { 154 final boolean isCurrentUser = UserHandle.myUserId() == ActivityManager.getCurrentUser(); 155 if (mServer == null && isCurrentUser) { 156 Log.d(TAG, "starting MTP server in " + (mPtpMode ? "PTP mode" : "MTP mode")); 157 mDatabase = new MtpDatabase(this, MediaProvider.EXTERNAL_VOLUME, 158 primary.getPath(), subdirs); 159 mServer = new MtpServer(mDatabase, mPtpMode); 160 mDatabase.setServer(mServer); 161 if (!mMtpDisabled) { 162 addStorageDevicesLocked(); 163 } 164 mServer.start(); 165 } else if (mServer != null && !isCurrentUser) { 166 Log.d(TAG, "no longer current user; shutting down MTP server"); 167 // Internally, kernel will close our FD, and server thread will 168 // handle cleanup. 169 mServer = null; 170 mDatabase.setServer(null); 171 } 172 } 173 174 @Override onDestroy()175 public void onDestroy() { 176 mStorageManager.unregisterListener(mStorageEventListener); 177 if (mDatabase != null) { 178 mDatabase.setServer(null); 179 } 180 } 181 182 private final IMtpService.Stub mBinder = 183 new IMtpService.Stub() { 184 public void sendObjectAdded(int objectHandle) { 185 synchronized (mBinder) { 186 if (mServer != null) { 187 mServer.sendObjectAdded(objectHandle); 188 } 189 } 190 } 191 192 public void sendObjectRemoved(int objectHandle) { 193 synchronized (mBinder) { 194 if (mServer != null) { 195 mServer.sendObjectRemoved(objectHandle); 196 } 197 } 198 } 199 }; 200 201 @Override onBind(Intent intent)202 public IBinder onBind(Intent intent) { 203 return mBinder; 204 } 205 volumeMountedLocked(String path)206 private void volumeMountedLocked(String path) { 207 for (int i = 0; i < mVolumes.length; i++) { 208 StorageVolume volume = mVolumes[i]; 209 if (volume.getPath().equals(path)) { 210 mVolumeMap.put(path, volume); 211 if (!mMtpDisabled) { 212 // In PTP mode we support only primary storage 213 if (volume.isPrimary() || !mPtpMode) { 214 addStorageLocked(volume); 215 } 216 } 217 break; 218 } 219 } 220 } 221 addStorageLocked(StorageVolume volume)222 private void addStorageLocked(StorageVolume volume) { 223 MtpStorage storage = new MtpStorage(volume, getApplicationContext()); 224 mStorageMap.put(storage.getPath(), storage); 225 226 if (storage.getStorageId() == StorageVolume.STORAGE_ID_INVALID) { 227 Log.w(TAG, "Ignoring volume with invalid MTP storage ID: " + storage); 228 return; 229 } else { 230 Log.d(TAG, "Adding MTP storage 0x" + Integer.toHexString(storage.getStorageId()) 231 + " at " + storage.getPath()); 232 } 233 234 if (mDatabase != null) { 235 mDatabase.addStorage(storage); 236 } 237 if (mServer != null) { 238 mServer.addStorage(storage); 239 } 240 } 241 removeStorageLocked(StorageVolume volume)242 private void removeStorageLocked(StorageVolume volume) { 243 MtpStorage storage = mStorageMap.remove(volume.getPath()); 244 if (storage == null) { 245 Log.e(TAG, "Missing MtpStorage for " + volume.getPath()); 246 return; 247 } 248 249 Log.d(TAG, "Removing MTP storage " + Integer.toHexString(storage.getStorageId()) + " at " 250 + storage.getPath()); 251 if (mDatabase != null) { 252 mDatabase.removeStorage(storage); 253 } 254 if (mServer != null) { 255 mServer.removeStorage(storage); 256 } 257 } 258 } 259