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.UserManager;
37import android.os.storage.StorageManager;
38import android.provider.MediaStore;
39import android.util.Log;
40
41import com.android.internal.util.ArrayUtils;
42
43import java.io.File;
44import java.util.Arrays;
45
46public 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