• 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.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             mDatabase = new MtpDatabase(this, MediaProvider.EXTERNAL_VOLUME,
162                     primary.getPath(), subdirs);
163             manageServiceLocked();
164         }
165 
166         return START_STICKY;
167     }
168 
updateDisabledStateLocked()169     private void updateDisabledStateLocked() {
170         final boolean isCurrentUser = UserHandle.myUserId() == ActivityManager.getCurrentUser();
171         final KeyguardManager keyguardManager = (KeyguardManager) getSystemService(
172                 Context.KEYGUARD_SERVICE);
173         mMtpDisabled = (keyguardManager.isKeyguardLocked() && keyguardManager.isKeyguardSecure())
174                 || !isCurrentUser;
175         if (LOGD) {
176             Log.d(TAG, "updating state; isCurrentUser=" + isCurrentUser + ", mMtpLocked="
177                     + mMtpDisabled);
178         }
179     }
180 
181     /**
182      * Manage {@link #mServer}, creating only when running as the current user.
183      */
manageServiceLocked()184     private void manageServiceLocked() {
185         final boolean isCurrentUser = UserHandle.myUserId() == ActivityManager.getCurrentUser();
186         if (mServer == null && isCurrentUser) {
187             Log.d(TAG, "starting MTP server in " + (mPtpMode ? "PTP mode" : "MTP mode"));
188             mServer = new MtpServer(mDatabase, mPtpMode);
189             if (!mMtpDisabled) {
190                 addStorageDevicesLocked();
191             }
192             mServer.start();
193         } else if (mServer != null && !isCurrentUser) {
194             Log.d(TAG, "no longer current user; shutting down MTP server");
195             // Internally, kernel will close our FD, and server thread will
196             // handle cleanup.
197             mServer = null;
198         }
199     }
200 
201     @Override
onDestroy()202     public void onDestroy() {
203         unregisterReceiver(mReceiver);
204         mStorageManager.unregisterListener(mStorageEventListener);
205     }
206 
207     private final IMtpService.Stub mBinder =
208             new IMtpService.Stub() {
209         public void sendObjectAdded(int objectHandle) {
210             synchronized (mBinder) {
211                 if (mServer != null) {
212                     mServer.sendObjectAdded(objectHandle);
213                 }
214             }
215         }
216 
217         public void sendObjectRemoved(int objectHandle) {
218             synchronized (mBinder) {
219                 if (mServer != null) {
220                     mServer.sendObjectRemoved(objectHandle);
221                 }
222             }
223         }
224     };
225 
226     @Override
onBind(Intent intent)227     public IBinder onBind(Intent intent) {
228         return mBinder;
229     }
230 
volumeMountedLocked(String path)231     private void volumeMountedLocked(String path) {
232         for (int i = 0; i < mVolumes.length; i++) {
233             StorageVolume volume = mVolumes[i];
234             if (volume.getPath().equals(path)) {
235                 mVolumeMap.put(path, volume);
236                 if (!mMtpDisabled) {
237                     // In PTP mode we support only primary storage
238                     if (volume.isPrimary() || !mPtpMode) {
239                         addStorageLocked(volume);
240                     }
241                 }
242                 break;
243             }
244         }
245     }
246 
addStorageLocked(StorageVolume volume)247     private void addStorageLocked(StorageVolume volume) {
248         MtpStorage storage = new MtpStorage(volume, getApplicationContext());
249         String path = storage.getPath();
250         mStorageMap.put(path, storage);
251 
252         Log.d(TAG, "addStorageLocked " + storage.getStorageId() + " " + path);
253         if (mDatabase != null) {
254             mDatabase.addStorage(storage);
255         }
256         if (mServer != null) {
257             mServer.addStorage(storage);
258         }
259     }
260 
removeStorageLocked(StorageVolume volume)261     private void removeStorageLocked(StorageVolume volume) {
262         MtpStorage storage = mStorageMap.remove(volume.getPath());
263         if (storage == null) {
264             Log.e(TAG, "no MtpStorage for " + volume.getPath());
265             return;
266         }
267 
268         Log.d(TAG, "removeStorageLocked " + storage.getStorageId() + " " + storage.getPath());
269         if (mDatabase != null) {
270             mDatabase.removeStorage(storage);
271         }
272         if (mServer != null) {
273             mServer.removeStorage(storage);
274         }
275     }
276 }
277