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