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.storage.StorageManager; 37 import android.provider.MediaStore; 38 import android.util.Log; 39 40 import java.io.File; 41 import java.util.Arrays; 42 43 public 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 openDatabase(String volumeName)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 scan(String[] directories, String volumeName)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 onCreate()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 onStartCommand(Intent intent, int flags, int startId)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 onDestroy()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 run()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 scanFile(String path, String mimeType)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 onBind(Intent intent)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 handleMessage(Message msg)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