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