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