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.content.res.Configuration; 25 import android.database.Cursor; 26 import android.media.IMediaScannerListener; 27 import android.media.IMediaScannerService; 28 import android.media.MediaScanner; 29 import android.net.Uri; 30 import android.os.Bundle; 31 import android.os.Environment; 32 import android.os.FileUtils; 33 import android.os.Handler; 34 import android.os.IBinder; 35 import android.os.Looper; 36 import android.os.Message; 37 import android.os.PowerManager; 38 import android.os.Process; 39 import android.os.SystemProperties; 40 import android.os.storage.StorageManager; 41 import android.os.storage.StorageVolume; 42 import android.provider.MediaStore; 43 import android.util.Log; 44 45 import java.io.File; 46 import java.util.Arrays; 47 import java.util.Locale; 48 49 public 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 openDatabase(String volumeName)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 createMediaScanner()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 scan(String[] directories, String volumeName)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 onCreate()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 onStartCommand(Intent intent, int flags, int startId)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 onDestroy()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 run()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 scanFile(String path, String mimeType)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 onBind(Intent intent)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 handleMessage(Message msg)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 }; 262 } 263 else if (MediaProvider.EXTERNAL_VOLUME.equals(volume)) { 264 // scan external storage volumes 265 directories = mExternalStoragePaths; 266 } 267 268 if (directories != null) { 269 if (false) Log.d(TAG, "start scanning volume " + volume + ": " 270 + Arrays.toString(directories)); 271 scan(directories, volume); 272 if (false) Log.d(TAG, "done scanning volume " + volume); 273 } 274 } 275 } catch (Exception e) { 276 Log.e(TAG, "Exception in handleMessage", e); 277 } 278 279 stopSelf(msg.arg1); 280 } 281 }; 282 } 283 284