1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.providers.media; 18 19 import static com.android.providers.media.scan.MediaScanner.REASON_DEMAND; 20 import static com.android.providers.media.scan.MediaScanner.REASON_MOUNTED; 21 import static com.android.providers.media.util.Logging.TAG; 22 23 import android.content.ContentProviderClient; 24 import android.content.ContentResolver; 25 import android.content.ContentValues; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.media.RingtoneManager; 29 import android.net.Uri; 30 import android.os.Trace; 31 import android.provider.MediaStore; 32 import android.util.Log; 33 34 import androidx.core.app.JobIntentService; 35 36 import com.android.providers.media.util.FileUtils; 37 38 import java.io.File; 39 import java.io.IOException; 40 41 public class MediaService extends JobIntentService { 42 private static final int JOB_ID = -300; 43 enqueueWork(Context context, Intent work)44 public static void enqueueWork(Context context, Intent work) { 45 enqueueWork(context, MediaService.class, JOB_ID, work); 46 } 47 48 @Override onHandleWork(Intent intent)49 protected void onHandleWork(Intent intent) { 50 Trace.beginSection(intent.getAction()); 51 if (Log.isLoggable(TAG, Log.INFO)) { 52 Log.i(TAG, "Begin " + intent); 53 } 54 try { 55 switch (intent.getAction()) { 56 case Intent.ACTION_LOCALE_CHANGED: { 57 onLocaleChanged(); 58 break; 59 } 60 case Intent.ACTION_PACKAGE_FULLY_REMOVED: 61 case Intent.ACTION_PACKAGE_DATA_CLEARED: { 62 final String packageName = intent.getData().getSchemeSpecificPart(); 63 onPackageOrphaned(packageName); 64 break; 65 } 66 case Intent.ACTION_MEDIA_SCANNER_SCAN_FILE: { 67 onScanFile(this, intent.getData()); 68 break; 69 } 70 case Intent.ACTION_MEDIA_MOUNTED: { 71 onScanVolume(this, intent.getData(), REASON_MOUNTED); 72 break; 73 } 74 default: { 75 Log.w(TAG, "Unknown intent " + intent); 76 break; 77 } 78 } 79 } catch (Exception e) { 80 Log.w(TAG, "Failed operation " + intent, e); 81 } finally { 82 if (Log.isLoggable(TAG, Log.INFO)) { 83 Log.i(TAG, "End " + intent); 84 } 85 Trace.endSection(); 86 } 87 } 88 onLocaleChanged()89 private void onLocaleChanged() { 90 try (ContentProviderClient cpc = getContentResolver() 91 .acquireContentProviderClient(MediaStore.AUTHORITY)) { 92 ((MediaProvider) cpc.getLocalContentProvider()).onLocaleChanged(); 93 } 94 } 95 onPackageOrphaned(String packageName)96 private void onPackageOrphaned(String packageName) { 97 try (ContentProviderClient cpc = getContentResolver() 98 .acquireContentProviderClient(MediaStore.AUTHORITY)) { 99 ((MediaProvider) cpc.getLocalContentProvider()).onPackageOrphaned(packageName); 100 } 101 } 102 onScanVolume(Context context, Uri uri, int reason)103 private static void onScanVolume(Context context, Uri uri, int reason) 104 throws IOException { 105 final File file = new File(uri.getPath()).getCanonicalFile(); 106 final String volumeName = FileUtils.getVolumeName(context, file); 107 108 onScanVolume(context, volumeName, reason); 109 } 110 onScanVolume(Context context, String volumeName, int reason)111 public static void onScanVolume(Context context, String volumeName, int reason) 112 throws IOException { 113 // If we're about to scan any external storage, scan internal first 114 // to ensure that we have ringtones ready to roll before a possibly very 115 // long external storage scan 116 if (!MediaStore.VOLUME_INTERNAL.equals(volumeName)) { 117 onScanVolume(context, MediaStore.VOLUME_INTERNAL, reason); 118 RingtoneManager.ensureDefaultRingtones(context); 119 } 120 121 // Resolve the Uri that we should use for all broadcast intents related 122 // to this volume; we do this once to ensure we can deliver all events 123 // in the situation where a volume is ejected mid-scan 124 final Uri broadcastUri; 125 if (!MediaStore.VOLUME_INTERNAL.equals(volumeName)) { 126 broadcastUri = Uri.fromFile(FileUtils.getVolumePath(context, volumeName)); 127 } else { 128 broadcastUri = null; 129 } 130 131 try (ContentProviderClient cpc = context.getContentResolver() 132 .acquireContentProviderClient(MediaStore.AUTHORITY)) { 133 final MediaProvider provider = ((MediaProvider) cpc.getLocalContentProvider()); 134 provider.attachVolume(volumeName, /* validate */ true); 135 136 final ContentResolver resolver = ContentResolver.wrap(cpc.getLocalContentProvider()); 137 138 ContentValues values = new ContentValues(); 139 values.put(MediaStore.MEDIA_SCANNER_VOLUME, volumeName); 140 Uri scanUri = resolver.insert(MediaStore.getMediaScannerUri(), values); 141 142 if (broadcastUri != null) { 143 context.sendBroadcast( 144 new Intent(Intent.ACTION_MEDIA_SCANNER_STARTED, broadcastUri)); 145 } 146 147 for (File dir : FileUtils.getVolumeScanPaths(context, volumeName)) { 148 provider.scanDirectory(dir, reason); 149 } 150 151 resolver.delete(scanUri, null, null); 152 153 } finally { 154 if (broadcastUri != null) { 155 context.sendBroadcast( 156 new Intent(Intent.ACTION_MEDIA_SCANNER_FINISHED, broadcastUri)); 157 } 158 } 159 } 160 onScanFile(Context context, Uri uri)161 private static Uri onScanFile(Context context, Uri uri) throws IOException { 162 final File file = new File(uri.getPath()).getCanonicalFile(); 163 try (ContentProviderClient cpc = context.getContentResolver() 164 .acquireContentProviderClient(MediaStore.AUTHORITY)) { 165 final MediaProvider provider = ((MediaProvider) cpc.getLocalContentProvider()); 166 return provider.scanFile(file, REASON_DEMAND); 167 } 168 } 169 } 170