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