Home | History | Annotate | Download | only in media
      1 /* //device/content/providers/media/src/com/android/providers/media/MediaScannerService.java
      2 **
      3 ** Copyright 2007, The Android Open Source Project
      4 **
      5 ** Licensed under the Apache License, Version 2.0 (the "License");
      6 ** you may not use this file except in compliance with the License.
      7 ** You may obtain a copy of the License at
      8 **
      9 **     http://www.apache.org/licenses/LICENSE-2.0
     10 **
     11 ** Unless required by applicable law or agreed to in writing, software
     12 ** distributed under the License is distributed on an "AS IS" BASIS,
     13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 ** See the License for the specific language governing permissions and
     15 ** limitations under the License.
     16 */
     17 
     18 package com.android.providers.media;
     19 
     20 import android.app.Service;
     21 import android.content.ContentProviderClient;
     22 import android.content.ContentValues;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.media.IMediaScannerListener;
     26 import android.media.IMediaScannerService;
     27 import android.media.MediaScanner;
     28 import android.net.Uri;
     29 import android.os.Bundle;
     30 import android.os.Environment;
     31 import android.os.Handler;
     32 import android.os.IBinder;
     33 import android.os.Looper;
     34 import android.os.Message;
     35 import android.os.PowerManager;
     36 import android.os.Process;
     37 import android.os.UserManager;
     38 import android.os.storage.StorageManager;
     39 import android.provider.MediaStore;
     40 import android.util.Log;
     41 
     42 import com.android.internal.util.ArrayUtils;
     43 
     44 import java.io.File;
     45 import java.util.Arrays;
     46 
     47 public class MediaScannerService extends Service implements Runnable {
     48     private static final String TAG = "MediaScannerService";
     49 
     50     private volatile Looper mServiceLooper;
     51     private volatile ServiceHandler mServiceHandler;
     52     private PowerManager.WakeLock mWakeLock;
     53     private String[] mExternalStoragePaths;
     54 
     55     private void openDatabase(String volumeName) {
     56         try {
     57             ContentValues values = new ContentValues();
     58             values.put("name", volumeName);
     59             getContentResolver().insert(Uri.parse("content://media/"), values);
     60         } catch (IllegalArgumentException ex) {
     61             Log.w(TAG, "failed to open media database");
     62         }
     63     }
     64 
     65     private void scan(String[] directories, String volumeName) {
     66         Uri uri = Uri.parse("file://" + directories[0]);
     67         // don't sleep while scanning
     68         mWakeLock.acquire();
     69 
     70         try {
     71             ContentValues values = new ContentValues();
     72             values.put(MediaStore.MEDIA_SCANNER_VOLUME, volumeName);
     73             Uri scanUri = getContentResolver().insert(MediaStore.getMediaScannerUri(), values);
     74 
     75             sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_STARTED, uri));
     76 
     77             try {
     78                 if (volumeName.equals(MediaProvider.EXTERNAL_VOLUME)) {
     79                     openDatabase(volumeName);
     80                 }
     81 
     82                 try (MediaScanner scanner = new MediaScanner(this, volumeName)) {
     83                     scanner.scanDirectories(directories);
     84                 }
     85             } catch (Exception e) {
     86                 Log.e(TAG, "exception in MediaScanner.scan()", e);
     87             }
     88 
     89             getContentResolver().delete(scanUri, null, null);
     90 
     91         } finally {
     92             sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_FINISHED, uri));
     93             mWakeLock.release();
     94         }
     95     }
     96 
     97     @Override
     98     public void onCreate() {
     99         PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
    100         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
    101         StorageManager storageManager = (StorageManager)getSystemService(Context.STORAGE_SERVICE);
    102         mExternalStoragePaths = storageManager.getVolumePaths();
    103 
    104         // Start up the thread running the service.  Note that we create a
    105         // separate thread because the service normally runs in the process's
    106         // main thread, which we don't want to block.
    107         Thread thr = new Thread(null, this, "MediaScannerService");
    108         thr.start();
    109     }
    110 
    111     @Override
    112     public int onStartCommand(Intent intent, int flags, int startId) {
    113         while (mServiceHandler == null) {
    114             synchronized (this) {
    115                 try {
    116                     wait(100);
    117                 } catch (InterruptedException e) {
    118                 }
    119             }
    120         }
    121 
    122         if (intent == null) {
    123             Log.e(TAG, "Intent is null in onStartCommand: ",
    124                 new NullPointerException());
    125             return Service.START_NOT_STICKY;
    126         }
    127 
    128         Message msg = mServiceHandler.obtainMessage();
    129         msg.arg1 = startId;
    130         msg.obj = intent.getExtras();
    131         mServiceHandler.sendMessage(msg);
    132 
    133         // Try again later if we are killed before we can finish scanning.
    134         return Service.START_REDELIVER_INTENT;
    135     }
    136 
    137     @Override
    138     public void onDestroy() {
    139         // Make sure thread has started before telling it to quit.
    140         while (mServiceLooper == null) {
    141             synchronized (this) {
    142                 try {
    143                     wait(100);
    144                 } catch (InterruptedException e) {
    145                 }
    146             }
    147         }
    148         mServiceLooper.quit();
    149     }
    150 
    151     @Override
    152     public void run() {
    153         // reduce priority below other background threads to avoid interfering
    154         // with other services at boot time.
    155         Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND +
    156                 Process.THREAD_PRIORITY_LESS_FAVORABLE);
    157         Looper.prepare();
    158 
    159         mServiceLooper = Looper.myLooper();
    160         mServiceHandler = new ServiceHandler();
    161 
    162         Looper.loop();
    163     }
    164 
    165     private Uri scanFile(String path, String mimeType) {
    166         String volumeName = MediaProvider.EXTERNAL_VOLUME;
    167 
    168         try (MediaScanner scanner = new MediaScanner(this, volumeName)) {
    169             // make sure the file path is in canonical form
    170             String canonicalPath = new File(path).getCanonicalPath();
    171             return scanner.scanSingleFile(canonicalPath, mimeType);
    172         } catch (Exception e) {
    173             Log.e(TAG, "bad path " + path + " in scanFile()", e);
    174             return null;
    175         }
    176     }
    177 
    178     @Override
    179     public IBinder onBind(Intent intent) {
    180         return mBinder;
    181     }
    182 
    183     private final IMediaScannerService.Stub mBinder =
    184             new IMediaScannerService.Stub() {
    185         public void requestScanFile(String path, String mimeType, IMediaScannerListener listener) {
    186             if (false) {
    187                 Log.d(TAG, "IMediaScannerService.scanFile: " + path + " mimeType: " + mimeType);
    188             }
    189             Bundle args = new Bundle();
    190             args.putString("filepath", path);
    191             args.putString("mimetype", mimeType);
    192             if (listener != null) {
    193                 args.putIBinder("listener", listener.asBinder());
    194             }
    195             startService(new Intent(MediaScannerService.this,
    196                     MediaScannerService.class).putExtras(args));
    197         }
    198 
    199         public void scanFile(String path, String mimeType) {
    200             requestScanFile(path, mimeType, null);
    201         }
    202     };
    203 
    204     private final class ServiceHandler extends Handler {
    205         @Override
    206         public void handleMessage(Message msg) {
    207             Bundle arguments = (Bundle) msg.obj;
    208             if (arguments == null) {
    209                 Log.e(TAG, "null intent, b/20953950");
    210                 return;
    211             }
    212             String filePath = arguments.getString("filepath");
    213 
    214             try {
    215                 if (filePath != null) {
    216                     IBinder binder = arguments.getIBinder("listener");
    217                     IMediaScannerListener listener =
    218                             (binder == null ? null : IMediaScannerListener.Stub.asInterface(binder));
    219                     Uri uri = null;
    220                     try {
    221                         uri = scanFile(filePath, arguments.getString("mimetype"));
    222                     } catch (Exception e) {
    223                         Log.e(TAG, "Exception scanning file", e);
    224                     }
    225                     if (listener != null) {
    226                         listener.scanCompleted(filePath, uri);
    227                     }
    228                 } else if (arguments.getBoolean(MediaStore.RETRANSLATE_CALL)) {
    229                     ContentProviderClient mediaProvider = getBaseContext().getContentResolver()
    230                         .acquireContentProviderClient(MediaStore.AUTHORITY);
    231                     mediaProvider.call(MediaStore.RETRANSLATE_CALL, null, null);
    232                 } else {
    233                     String volume = arguments.getString("volume");
    234                     String[] directories = null;
    235 
    236                     if (MediaProvider.INTERNAL_VOLUME.equals(volume)) {
    237                         // scan internal media storage
    238                         directories = new String[] {
    239                                 Environment.getRootDirectory() + "/media",
    240                                 Environment.getOemDirectory() + "/media",
    241                                 Environment.getProductDirectory() + "/media",
    242                         };
    243                     }
    244                     else if (MediaProvider.EXTERNAL_VOLUME.equals(volume)) {
    245                         // scan external storage volumes
    246                         if (getSystemService(UserManager.class).isDemoUser()) {
    247                             directories = ArrayUtils.appendElement(String.class,
    248                                     mExternalStoragePaths,
    249                                     Environment.getDataPreloadsMediaDirectory().getAbsolutePath());
    250                         } else {
    251                             directories = mExternalStoragePaths;
    252                         }
    253                     }
    254 
    255                     if (directories != null) {
    256                         if (false) Log.d(TAG, "start scanning volume " + volume + ": "
    257                                 + Arrays.toString(directories));
    258                         scan(directories, volume);
    259                         if (false) Log.d(TAG, "done scanning volume " + volume);
    260                     }
    261                 }
    262             } catch (Exception e) {
    263                 Log.e(TAG, "Exception in handleMessage", e);
    264             }
    265 
    266             stopSelf(msg.arg1);
    267         }
    268     }
    269 }
    270