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.annotation.NonNull; 20 import android.app.ActivityManager; 21 import android.app.Service; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.pm.PackageManager; 25 import android.hardware.usb.UsbManager; 26 import android.Manifest; 27 import android.mtp.MtpDatabase; 28 import android.mtp.MtpServer; 29 import android.os.Build; 30 import android.os.Environment; 31 import android.os.IBinder; 32 import android.os.ParcelFileDescriptor; 33 import android.os.RemoteException; 34 import android.os.ServiceManager; 35 import android.os.UserHandle; 36 import android.os.storage.StorageEventListener; 37 import android.os.storage.StorageManager; 38 import android.os.storage.StorageVolume; 39 import android.util.Log; 40 41 import com.android.internal.annotations.GuardedBy; 42 import com.android.internal.util.Preconditions; 43 import android.hardware.usb.IUsbManager; 44 45 import java.io.File; 46 import java.io.FileDescriptor; 47 import java.util.HashMap; 48 49 /** 50 * The singleton service backing instances of MtpServer that are started for the foreground user. 51 * The service has the responsibility of retrieving user storage information and managing server 52 * lifetime. 53 */ 54 public class MtpService extends Service { 55 private static final String TAG = "MtpService"; 56 private static final boolean LOGD = false; 57 58 // We restrict PTP to these subdirectories 59 private static final String[] PTP_DIRECTORIES = new String[] { 60 Environment.DIRECTORY_DCIM, 61 Environment.DIRECTORY_PICTURES, 62 }; 63 64 private final StorageEventListener mStorageEventListener = new StorageEventListener() { 65 @Override 66 public void onStorageStateChanged(String path, String oldState, String newState) { 67 synchronized (MtpService.this) { 68 Log.d(TAG, "onStorageStateChanged " + path + " " + oldState + " -> " + newState); 69 if (Environment.MEDIA_MOUNTED.equals(newState)) { 70 for (int i = 0; i < mVolumes.length; i++) { 71 StorageVolume volume = mVolumes[i]; 72 if (volume.getPath().equals(path)) { 73 mVolumeMap.put(path, volume); 74 if (mUnlocked && (volume.isPrimary() || !mPtpMode)) { 75 addStorage(volume); 76 } 77 break; 78 } 79 } 80 } else if (Environment.MEDIA_MOUNTED.equals(oldState)) { 81 if (mVolumeMap.containsKey(path)) { 82 removeStorage(mVolumeMap.remove(path)); 83 } 84 } 85 } 86 } 87 }; 88 89 /** 90 * Static state of MtpServer. MtpServer opens FD for MTP driver internally and we cannot open 91 * multiple MtpServer at the same time. The static field used to handle the case where MtpServer 92 * lives beyond the lifetime of MtpService. 93 * 94 * Lock MtpService.this before locking MtpService.class if needed. Otherwise it goes to 95 * deadlock. 96 */ 97 @GuardedBy("MtpService.class") 98 private static ServerHolder sServerHolder; 99 100 private StorageManager mStorageManager; 101 102 @GuardedBy("this") 103 private boolean mUnlocked; 104 @GuardedBy("this") 105 private boolean mPtpMode; 106 107 // A map of user volumes that are currently mounted. 108 @GuardedBy("this") 109 private HashMap<String, StorageVolume> mVolumeMap; 110 111 // All user volumes in existence, in any state. 112 @GuardedBy("this") 113 private StorageVolume[] mVolumes; 114 115 @Override 116 public void onCreate() { 117 mVolumes = StorageManager.getVolumeList(getUserId(), 0); 118 mVolumeMap = new HashMap<>(); 119 120 mStorageManager = this.getSystemService(StorageManager.class); 121 mStorageManager.registerListener(mStorageEventListener); 122 } 123 124 @Override 125 public void onDestroy() { 126 mStorageManager.unregisterListener(mStorageEventListener); 127 synchronized (MtpService.class) { 128 if (sServerHolder != null) { 129 sServerHolder.database.setServer(null); 130 } 131 } 132 } 133 134 @Override 135 public synchronized int onStartCommand(Intent intent, int flags, int startId) { 136 mUnlocked = intent.getBooleanExtra(UsbManager.USB_DATA_UNLOCKED, false); 137 mPtpMode = intent.getBooleanExtra(UsbManager.USB_FUNCTION_PTP, false); 138 139 for (StorageVolume v : mVolumes) { 140 if (v.getState().equals(Environment.MEDIA_MOUNTED)) { 141 mVolumeMap.put(v.getPath(), v); 142 } 143 } 144 String[] subdirs = null; 145 if (mPtpMode) { 146 Environment.UserEnvironment env = new Environment.UserEnvironment(getUserId()); 147 int count = PTP_DIRECTORIES.length; 148 subdirs = new String[count]; 149 for (int i = 0; i < count; i++) { 150 File file = env.buildExternalStoragePublicDirs(PTP_DIRECTORIES[i])[0]; 151 // make sure this directory exists 152 file.mkdirs(); 153 subdirs[i] = file.getName(); 154 } 155 } 156 final StorageVolume primary = StorageManager.getPrimaryVolume(mVolumes); 157 startServer(primary, subdirs); 158 return START_REDELIVER_INTENT; 159 } 160 161 private synchronized void startServer(StorageVolume primary, String[] subdirs) { 162 if (!(UserHandle.myUserId() == ActivityManager.getCurrentUser())) { 163 return; 164 } 165 synchronized (MtpService.class) { 166 if (sServerHolder != null) { 167 if (LOGD) { 168 Log.d(TAG, "Cannot launch second MTP server."); 169 } 170 // Previously executed MtpServer is still running. It will be terminated 171 // because MTP device FD will become invalid soon. Also MtpService will get new 172 // intent after that when UsbDeviceManager configures USB with new state. 173 return; 174 } 175 176 Log.d(TAG, "starting MTP server in " + (mPtpMode ? "PTP mode" : "MTP mode") + 177 " with storage " + primary.getPath() + (mUnlocked ? " unlocked" : "") + " as user " + UserHandle.myUserId()); 178 179 final MtpDatabase database = new MtpDatabase(this, MediaProvider.EXTERNAL_VOLUME, subdirs); 180 String deviceSerialNumber = Build.getSerial(); 181 if (Build.UNKNOWN.equals(deviceSerialNumber)) { 182 deviceSerialNumber = "????????"; 183 } 184 IUsbManager usbMgr = IUsbManager.Stub.asInterface(ServiceManager.getService( 185 Context.USB_SERVICE)); 186 ParcelFileDescriptor controlFd = null; 187 try { 188 controlFd = usbMgr.getControlFd( 189 mPtpMode ? UsbManager.FUNCTION_PTP : UsbManager.FUNCTION_MTP); 190 } catch (RemoteException e) { 191 Log.e(TAG, "Error communicating with UsbManager: " + e); 192 } 193 FileDescriptor fd = null; 194 if (controlFd == null) { 195 Log.i(TAG, "Couldn't get control FD!"); 196 } else { 197 fd = controlFd.getFileDescriptor(); 198 } 199 200 final MtpServer server = 201 new MtpServer(database, fd, mPtpMode, 202 new OnServerTerminated(), Build.MANUFACTURER, 203 Build.MODEL, "1.0", deviceSerialNumber); 204 database.setServer(server); 205 sServerHolder = new ServerHolder(server, database); 206 207 // Add currently mounted and enabled storages to the server 208 if (mUnlocked) { 209 if (mPtpMode) { 210 addStorage(primary); 211 } else { 212 for (StorageVolume v : mVolumeMap.values()) { 213 addStorage(v); 214 } 215 } 216 } 217 server.start(); 218 } 219 } 220 221 private final IMtpService.Stub mBinder = 222 new IMtpService.Stub() { 223 }; 224 225 @Override 226 public IBinder onBind(Intent intent) { 227 return mBinder; 228 } 229 230 private void addStorage(StorageVolume volume) { 231 Log.v(TAG, "Adding MTP storage:" + volume.getPath()); 232 synchronized (this) { 233 if (sServerHolder != null) { 234 sServerHolder.database.addStorage(volume); 235 } 236 } 237 } 238 239 private void removeStorage(StorageVolume volume) { 240 synchronized (MtpService.class) { 241 if (sServerHolder != null) { 242 sServerHolder.database.removeStorage(volume); 243 } 244 } 245 } 246 247 private static class ServerHolder { 248 @NonNull final MtpServer server; 249 @NonNull final MtpDatabase database; 250 251 ServerHolder(@NonNull MtpServer server, @NonNull MtpDatabase database) { 252 Preconditions.checkNotNull(server); 253 Preconditions.checkNotNull(database); 254 this.server = server; 255 this.database = database; 256 } 257 258 void close() { 259 this.database.setServer(null); 260 } 261 } 262 263 private class OnServerTerminated implements Runnable { 264 @Override 265 public void run() { 266 synchronized (MtpService.class) { 267 if (sServerHolder == null) { 268 Log.e(TAG, "sServerHolder is unexpectedly null."); 269 return; 270 } 271 sServerHolder.close(); 272 sServerHolder = null; 273 } 274 } 275 } 276 } 277