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.Service;
21 import android.content.Intent;
22 import android.hardware.usb.UsbManager;
23 import android.mtp.MtpDatabase;
24 import android.mtp.MtpServer;
25 import android.mtp.MtpStorage;
26 import android.os.Environment;
27 import android.os.IBinder;
28 import android.os.UserHandle;
29 import android.os.storage.StorageEventListener;
30 import android.os.storage.StorageManager;
31 import android.os.storage.StorageVolume;
32 import android.util.Log;
33 
34 import java.io.File;
35 import java.util.HashMap;
36 
37 public class MtpService extends Service {
38     private static final String TAG = "MtpService";
39     private static final boolean LOGD = false;
40 
41     // We restrict PTP to these subdirectories
42     private static final String[] PTP_DIRECTORIES = new String[] {
43         Environment.DIRECTORY_DCIM,
44         Environment.DIRECTORY_PICTURES,
45     };
46 
addStorageDevicesLocked()47     private void addStorageDevicesLocked() {
48         if (mPtpMode) {
49             // In PTP mode we support only primary storage
50             final StorageVolume primary = StorageManager.getPrimaryVolume(mVolumes);
51             final String path = primary.getPath();
52             if (path != null) {
53                 String state = mStorageManager.getVolumeState(path);
54                 if (Environment.MEDIA_MOUNTED.equals(state)) {
55                     addStorageLocked(mVolumeMap.get(path));
56                 }
57             }
58         } else {
59             for (StorageVolume volume : mVolumeMap.values()) {
60                 addStorageLocked(volume);
61             }
62         }
63     }
64 
65     private final StorageEventListener mStorageEventListener = new StorageEventListener() {
66         @Override
67         public void onStorageStateChanged(String path, String oldState, String newState) {
68             synchronized (mBinder) {
69                 Log.d(TAG, "onStorageStateChanged " + path + " " + oldState + " -> " + newState);
70                 if (Environment.MEDIA_MOUNTED.equals(newState)) {
71                     volumeMountedLocked(path);
72                 } else if (Environment.MEDIA_MOUNTED.equals(oldState)) {
73                     StorageVolume volume = mVolumeMap.remove(path);
74                     if (volume != null) {
75                         removeStorageLocked(volume);
76                     }
77                 }
78             }
79         }
80     };
81 
82     private MtpDatabase mDatabase;
83     private MtpServer mServer;
84     private StorageManager mStorageManager;
85     /** Flag indicating if MTP is disabled due to keyguard */
86     private boolean mMtpDisabled;
87     private boolean mUnlocked;
88     private boolean mPtpMode;
89     private final HashMap<String, StorageVolume> mVolumeMap = new HashMap<String, StorageVolume>();
90     private final HashMap<String, MtpStorage> mStorageMap = new HashMap<String, MtpStorage>();
91     private StorageVolume[] mVolumes;
92 
93     @Override
onCreate()94     public void onCreate() {
95         mStorageManager = StorageManager.from(this);
96         synchronized (mBinder) {
97             updateDisabledStateLocked();
98             mStorageManager.registerListener(mStorageEventListener);
99             StorageVolume[] volumes = mStorageManager.getVolumeList();
100             mVolumes = volumes;
101             for (int i = 0; i < volumes.length; i++) {
102                 String path = volumes[i].getPath();
103                 String state = mStorageManager.getVolumeState(path);
104                 if (Environment.MEDIA_MOUNTED.equals(state)) {
105                     volumeMountedLocked(path);
106                 }
107             }
108         }
109     }
110 
111     @Override
onStartCommand(Intent intent, int flags, int startId)112     public int onStartCommand(Intent intent, int flags, int startId) {
113         mUnlocked = intent.getBooleanExtra(UsbManager.USB_DATA_UNLOCKED, false);
114         if (LOGD) { Log.d(TAG, "onStartCommand intent=" + intent + " mUnlocked=" + mUnlocked); }
115         synchronized (mBinder) {
116             updateDisabledStateLocked();
117             mPtpMode = (intent == null ? false
118                     : intent.getBooleanExtra(UsbManager.USB_FUNCTION_PTP, false));
119             String[] subdirs = null;
120             if (mPtpMode) {
121                 int count = PTP_DIRECTORIES.length;
122                 subdirs = new String[count];
123                 for (int i = 0; i < count; i++) {
124                     File file =
125                             Environment.getExternalStoragePublicDirectory(PTP_DIRECTORIES[i]);
126                     // make sure this directory exists
127                     file.mkdirs();
128                     subdirs[i] = file.getPath();
129                 }
130             }
131             final StorageVolume primary = StorageManager.getPrimaryVolume(mVolumes);
132             if (mDatabase != null) {
133                 mDatabase.setServer(null);
134             }
135             manageServiceLocked(primary, subdirs);
136         }
137 
138         return START_REDELIVER_INTENT;
139     }
140 
updateDisabledStateLocked()141     private void updateDisabledStateLocked() {
142         final boolean isCurrentUser = UserHandle.myUserId() == ActivityManager.getCurrentUser();
143         mMtpDisabled = !mUnlocked || !isCurrentUser;
144         if (LOGD) {
145             Log.d(TAG, "updating state; isCurrentUser=" + isCurrentUser + ", mMtpLocked="
146                     + mMtpDisabled);
147         }
148     }
149 
150     /**
151      * Manage {@link #mServer}, creating only when running as the current user.
152      */
manageServiceLocked(StorageVolume primary, String[] subdirs)153     private void manageServiceLocked(StorageVolume primary, String[] subdirs) {
154         final boolean isCurrentUser = UserHandle.myUserId() == ActivityManager.getCurrentUser();
155         if (mServer == null && isCurrentUser) {
156             Log.d(TAG, "starting MTP server in " + (mPtpMode ? "PTP mode" : "MTP mode"));
157             mDatabase = new MtpDatabase(this, MediaProvider.EXTERNAL_VOLUME,
158                     primary.getPath(), subdirs);
159             mServer = new MtpServer(mDatabase, mPtpMode);
160             mDatabase.setServer(mServer);
161             if (!mMtpDisabled) {
162                 addStorageDevicesLocked();
163             }
164             mServer.start();
165         } else if (mServer != null && !isCurrentUser) {
166             Log.d(TAG, "no longer current user; shutting down MTP server");
167             // Internally, kernel will close our FD, and server thread will
168             // handle cleanup.
169             mServer = null;
170             mDatabase.setServer(null);
171         }
172     }
173 
174     @Override
onDestroy()175     public void onDestroy() {
176         mStorageManager.unregisterListener(mStorageEventListener);
177         if (mDatabase != null) {
178             mDatabase.setServer(null);
179         }
180     }
181 
182     private final IMtpService.Stub mBinder =
183             new IMtpService.Stub() {
184         public void sendObjectAdded(int objectHandle) {
185             synchronized (mBinder) {
186                 if (mServer != null) {
187                     mServer.sendObjectAdded(objectHandle);
188                 }
189             }
190         }
191 
192         public void sendObjectRemoved(int objectHandle) {
193             synchronized (mBinder) {
194                 if (mServer != null) {
195                     mServer.sendObjectRemoved(objectHandle);
196                 }
197             }
198         }
199     };
200 
201     @Override
onBind(Intent intent)202     public IBinder onBind(Intent intent) {
203         return mBinder;
204     }
205 
volumeMountedLocked(String path)206     private void volumeMountedLocked(String path) {
207         for (int i = 0; i < mVolumes.length; i++) {
208             StorageVolume volume = mVolumes[i];
209             if (volume.getPath().equals(path)) {
210                 mVolumeMap.put(path, volume);
211                 if (!mMtpDisabled) {
212                     // In PTP mode we support only primary storage
213                     if (volume.isPrimary() || !mPtpMode) {
214                         addStorageLocked(volume);
215                     }
216                 }
217                 break;
218             }
219         }
220     }
221 
addStorageLocked(StorageVolume volume)222     private void addStorageLocked(StorageVolume volume) {
223         MtpStorage storage = new MtpStorage(volume, getApplicationContext());
224         mStorageMap.put(storage.getPath(), storage);
225 
226         if (storage.getStorageId() == StorageVolume.STORAGE_ID_INVALID) {
227             Log.w(TAG, "Ignoring volume with invalid MTP storage ID: " + storage);
228             return;
229         } else {
230             Log.d(TAG, "Adding MTP storage 0x" + Integer.toHexString(storage.getStorageId())
231                     + " at " + storage.getPath());
232         }
233 
234         if (mDatabase != null) {
235             mDatabase.addStorage(storage);
236         }
237         if (mServer != null) {
238             mServer.addStorage(storage);
239         }
240     }
241 
removeStorageLocked(StorageVolume volume)242     private void removeStorageLocked(StorageVolume volume) {
243         MtpStorage storage = mStorageMap.remove(volume.getPath());
244         if (storage == null) {
245             Log.e(TAG, "Missing MtpStorage for " + volume.getPath());
246             return;
247         }
248 
249         Log.d(TAG, "Removing MTP storage " + Integer.toHexString(storage.getStorageId()) + " at "
250                 + storage.getPath());
251         if (mDatabase != null) {
252             mDatabase.removeStorage(storage);
253         }
254         if (mServer != null) {
255             mServer.removeStorage(storage);
256         }
257     }
258 }
259