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