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