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