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.annotation.NonNull;
     20 import android.app.ActivityManager;
     21 import android.app.Service;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.pm.PackageManager;
     25 import android.hardware.usb.UsbManager;
     26 import android.Manifest;
     27 import android.mtp.MtpDatabase;
     28 import android.mtp.MtpServer;
     29 import android.os.Build;
     30 import android.os.Environment;
     31 import android.os.IBinder;
     32 import android.os.ParcelFileDescriptor;
     33 import android.os.RemoteException;
     34 import android.os.ServiceManager;
     35 import android.os.UserHandle;
     36 import android.os.storage.StorageEventListener;
     37 import android.os.storage.StorageManager;
     38 import android.os.storage.StorageVolume;
     39 import android.util.Log;
     40 
     41 import com.android.internal.annotations.GuardedBy;
     42 import com.android.internal.util.Preconditions;
     43 import android.hardware.usb.IUsbManager;
     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                     for (int i = 0; i < mVolumes.length; i++) {
     71                         StorageVolume volume = mVolumes[i];
     72                         if (volume.getPath().equals(path)) {
     73                             mVolumeMap.put(path, volume);
     74                             if (mUnlocked && (volume.isPrimary() || !mPtpMode)) {
     75                                 addStorage(volume);
     76                             }
     77                             break;
     78                         }
     79                     }
     80                 } else if (Environment.MEDIA_MOUNTED.equals(oldState)) {
     81                     if (mVolumeMap.containsKey(path)) {
     82                         removeStorage(mVolumeMap.remove(path));
     83                     }
     84                 }
     85             }
     86         }
     87     };
     88 
     89     /**
     90      * Static state of MtpServer. MtpServer opens FD for MTP driver internally and we cannot open
     91      * multiple MtpServer at the same time. The static field used to handle the case where MtpServer
     92      * lives beyond the lifetime of MtpService.
     93      *
     94      * Lock MtpService.this before locking MtpService.class if needed. Otherwise it goes to
     95      * deadlock.
     96      */
     97     @GuardedBy("MtpService.class")
     98     private static ServerHolder sServerHolder;
     99 
    100     private StorageManager mStorageManager;
    101 
    102     @GuardedBy("this")
    103     private boolean mUnlocked;
    104     @GuardedBy("this")
    105     private boolean mPtpMode;
    106 
    107     // A map of user volumes that are currently mounted.
    108     @GuardedBy("this")
    109     private HashMap<String, StorageVolume> mVolumeMap;
    110 
    111     // All user volumes in existence, in any state.
    112     @GuardedBy("this")
    113     private StorageVolume[] mVolumes;
    114 
    115     @Override
    116     public void onCreate() {
    117         mVolumes = StorageManager.getVolumeList(getUserId(), 0);
    118         mVolumeMap = new HashMap<>();
    119 
    120         mStorageManager = this.getSystemService(StorageManager.class);
    121         mStorageManager.registerListener(mStorageEventListener);
    122     }
    123 
    124     @Override
    125     public void onDestroy() {
    126         mStorageManager.unregisterListener(mStorageEventListener);
    127         synchronized (MtpService.class) {
    128             if (sServerHolder != null) {
    129                 sServerHolder.database.setServer(null);
    130             }
    131         }
    132     }
    133 
    134     @Override
    135     public synchronized int onStartCommand(Intent intent, int flags, int startId) {
    136         mUnlocked = intent.getBooleanExtra(UsbManager.USB_DATA_UNLOCKED, false);
    137         mPtpMode = intent.getBooleanExtra(UsbManager.USB_FUNCTION_PTP, false);
    138 
    139         for (StorageVolume v : mVolumes) {
    140             if (v.getState().equals(Environment.MEDIA_MOUNTED)) {
    141                 mVolumeMap.put(v.getPath(), v);
    142             }
    143         }
    144         String[] subdirs = null;
    145         if (mPtpMode) {
    146             Environment.UserEnvironment env = new Environment.UserEnvironment(getUserId());
    147             int count = PTP_DIRECTORIES.length;
    148             subdirs = new String[count];
    149             for (int i = 0; i < count; i++) {
    150                 File file = env.buildExternalStoragePublicDirs(PTP_DIRECTORIES[i])[0];
    151                 // make sure this directory exists
    152                 file.mkdirs();
    153                 subdirs[i] = file.getName();
    154             }
    155         }
    156         final StorageVolume primary = StorageManager.getPrimaryVolume(mVolumes);
    157         startServer(primary, subdirs);
    158         return START_REDELIVER_INTENT;
    159     }
    160 
    161     private synchronized void startServer(StorageVolume primary, String[] subdirs) {
    162         if (!(UserHandle.myUserId() == ActivityManager.getCurrentUser())) {
    163             return;
    164         }
    165         synchronized (MtpService.class) {
    166             if (sServerHolder != null) {
    167                 if (LOGD) {
    168                     Log.d(TAG, "Cannot launch second MTP server.");
    169                 }
    170                 // Previously executed MtpServer is still running. It will be terminated
    171                 // because MTP device FD will become invalid soon. Also MtpService will get new
    172                 // intent after that when UsbDeviceManager configures USB with new state.
    173                 return;
    174             }
    175 
    176             Log.d(TAG, "starting MTP server in " + (mPtpMode ? "PTP mode" : "MTP mode") +
    177                     " with storage " + primary.getPath() + (mUnlocked ? " unlocked" : "") + " as user " + UserHandle.myUserId());
    178 
    179             final MtpDatabase database = new MtpDatabase(this, MediaProvider.EXTERNAL_VOLUME, subdirs);
    180             String deviceSerialNumber = Build.getSerial();
    181             if (Build.UNKNOWN.equals(deviceSerialNumber)) {
    182                 deviceSerialNumber = "????????";
    183             }
    184             IUsbManager usbMgr = IUsbManager.Stub.asInterface(ServiceManager.getService(
    185                     Context.USB_SERVICE));
    186             ParcelFileDescriptor controlFd = null;
    187             try {
    188                 controlFd = usbMgr.getControlFd(
    189                         mPtpMode ? UsbManager.FUNCTION_PTP : UsbManager.FUNCTION_MTP);
    190             } catch (RemoteException e) {
    191                 Log.e(TAG, "Error communicating with UsbManager: " + e);
    192             }
    193             FileDescriptor fd = null;
    194             if (controlFd == null) {
    195                 Log.i(TAG, "Couldn't get control FD!");
    196             } else {
    197                 fd = controlFd.getFileDescriptor();
    198             }
    199 
    200             final MtpServer server =
    201                     new MtpServer(database, fd, mPtpMode,
    202                             new OnServerTerminated(), Build.MANUFACTURER,
    203                             Build.MODEL, "1.0", deviceSerialNumber);
    204             database.setServer(server);
    205             sServerHolder = new ServerHolder(server, database);
    206 
    207             // Add currently mounted and enabled storages to the server
    208             if (mUnlocked) {
    209                 if (mPtpMode) {
    210                     addStorage(primary);
    211                 } else {
    212                     for (StorageVolume v : mVolumeMap.values()) {
    213                         addStorage(v);
    214                     }
    215                 }
    216             }
    217             server.start();
    218         }
    219     }
    220 
    221     private final IMtpService.Stub mBinder =
    222             new IMtpService.Stub() {
    223             };
    224 
    225     @Override
    226     public IBinder onBind(Intent intent) {
    227         return mBinder;
    228     }
    229 
    230     private void addStorage(StorageVolume volume) {
    231         Log.v(TAG, "Adding MTP storage:" + volume.getPath());
    232         synchronized (this) {
    233             if (sServerHolder != null) {
    234                 sServerHolder.database.addStorage(volume);
    235             }
    236         }
    237     }
    238 
    239     private void removeStorage(StorageVolume volume) {
    240         synchronized (MtpService.class) {
    241             if (sServerHolder != null) {
    242                 sServerHolder.database.removeStorage(volume);
    243             }
    244         }
    245     }
    246 
    247     private static class ServerHolder {
    248         @NonNull final MtpServer server;
    249         @NonNull final MtpDatabase database;
    250 
    251         ServerHolder(@NonNull MtpServer server, @NonNull MtpDatabase database) {
    252             Preconditions.checkNotNull(server);
    253             Preconditions.checkNotNull(database);
    254             this.server = server;
    255             this.database = database;
    256         }
    257 
    258         void close() {
    259             this.database.setServer(null);
    260         }
    261     }
    262 
    263     private class OnServerTerminated implements Runnable {
    264         @Override
    265         public void run() {
    266             synchronized (MtpService.class) {
    267                 if (sServerHolder == null) {
    268                     Log.e(TAG, "sServerHolder is unexpectedly null.");
    269                     return;
    270                 }
    271                 sServerHolder.close();
    272                 sServerHolder = null;
    273             }
    274         }
    275     }
    276 }
    277