• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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                 for (StorageVolume v : mVolumeMap.values()) {
208                     addStorage(v);
209                 }
210             }
211             server.start();
212         }
213     }
214 
215     @Override
onBind(Intent intent)216     public IBinder onBind(Intent intent) {
217         return new Binder();
218     }
219 
addStorage(StorageVolume volume)220     private void addStorage(StorageVolume volume) {
221         Log.v(TAG, "Adding MTP storage:" + volume.getPath());
222         synchronized (MtpService.class) {
223             if (sServerHolder != null) {
224                 sServerHolder.database.addStorage(volume);
225             }
226         }
227     }
228 
removeStorage(StorageVolume volume)229     private void removeStorage(StorageVolume volume) {
230         synchronized (MtpService.class) {
231             if (sServerHolder != null) {
232                 sServerHolder.database.removeStorage(volume);
233             }
234         }
235     }
236 
237     private static class ServerHolder {
238         @NonNull final MtpServer server;
239         @NonNull final MtpDatabase database;
240 
ServerHolder(@onNull MtpServer server, @NonNull MtpDatabase database)241         ServerHolder(@NonNull MtpServer server, @NonNull MtpDatabase database) {
242             Preconditions.checkNotNull(server);
243             Preconditions.checkNotNull(database);
244             this.server = server;
245             this.database = database;
246         }
247 
close()248         void close() {
249             this.database.setServer(null);
250         }
251     }
252 
253     private class OnServerTerminated implements Runnable {
254         @Override
run()255         public void run() {
256             synchronized (MtpService.class) {
257                 if (sServerHolder == null) {
258                     Log.e(TAG, "sServerHolder is unexpectedly null.");
259                     return;
260                 }
261                 sServerHolder.close();
262                 sServerHolder = null;
263             }
264         }
265     }
266 }
267