Home | History | Annotate | Download | only in media
      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 
     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
    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
    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 
    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      */
    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
    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
    227     public IBinder onBind(Intent intent) {
    228         return mBinder;
    229     }
    230 
    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 
    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 
    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