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 android.media.RingtoneManager.TYPE_ALARM; 20 import static android.media.RingtoneManager.TYPE_NOTIFICATION; 21 import static android.media.RingtoneManager.TYPE_RINGTONE; 22 23 import static com.android.providers.media.MediaProvider.TAG; 24 25 import android.app.IntentService; 26 import android.content.ContentProviderClient; 27 import android.content.ContentResolver; 28 import android.content.ContentUris; 29 import android.content.ContentValues; 30 import android.content.Context; 31 import android.content.Intent; 32 import android.database.Cursor; 33 import android.media.RingtoneManager; 34 import android.net.Uri; 35 import android.os.Environment; 36 import android.os.PowerManager; 37 import android.os.SystemProperties; 38 import android.os.Trace; 39 import android.provider.MediaStore; 40 import android.provider.MediaStore.MediaColumns; 41 import android.provider.Settings; 42 import android.util.Log; 43 44 import com.android.providers.media.scan.MediaScanner; 45 46 import java.io.File; 47 import java.io.FileNotFoundException; 48 import java.io.IOException; 49 import java.util.Collection; 50 51 public class MediaService extends IntentService { MediaService()52 public MediaService() { 53 super(TAG); 54 } 55 56 private PowerManager.WakeLock mWakeLock; 57 58 @Override onCreate()59 public void onCreate() { 60 super.onCreate(); 61 62 mWakeLock = getSystemService(PowerManager.class).newWakeLock( 63 PowerManager.PARTIAL_WAKE_LOCK, TAG); 64 } 65 66 @Override onHandleIntent(Intent intent)67 protected void onHandleIntent(Intent intent) { 68 mWakeLock.acquire(); 69 Trace.traceBegin(Trace.TRACE_TAG_DATABASE, intent.getAction()); 70 if (Log.isLoggable(TAG, Log.INFO)) { 71 Log.i(TAG, "Begin " + intent); 72 } 73 try { 74 switch (intent.getAction()) { 75 case Intent.ACTION_LOCALE_CHANGED: { 76 onLocaleChanged(); 77 break; 78 } 79 case Intent.ACTION_PACKAGE_FULLY_REMOVED: 80 case Intent.ACTION_PACKAGE_DATA_CLEARED: { 81 final String packageName = intent.getData().getSchemeSpecificPart(); 82 onPackageOrphaned(packageName); 83 break; 84 } 85 case Intent.ACTION_MEDIA_MOUNTED: { 86 onScanVolume(this, intent.getData()); 87 break; 88 } 89 case Intent.ACTION_MEDIA_SCANNER_SCAN_FILE: { 90 onScanFile(this, intent.getData()); 91 break; 92 } 93 default: { 94 Log.w(TAG, "Unknown intent " + intent); 95 break; 96 } 97 } 98 } catch (Exception e) { 99 Log.w(TAG, "Failed operation " + intent, e); 100 } finally { 101 if (Log.isLoggable(TAG, Log.INFO)) { 102 Log.i(TAG, "End " + intent); 103 } 104 Trace.traceEnd(Trace.TRACE_TAG_DATABASE); 105 mWakeLock.release(); 106 } 107 } 108 onLocaleChanged()109 private void onLocaleChanged() { 110 try (ContentProviderClient cpc = getContentResolver() 111 .acquireContentProviderClient(MediaStore.AUTHORITY)) { 112 ((MediaProvider) cpc.getLocalContentProvider()).onLocaleChanged(); 113 } 114 } 115 onPackageOrphaned(String packageName)116 private void onPackageOrphaned(String packageName) { 117 try (ContentProviderClient cpc = getContentResolver() 118 .acquireContentProviderClient(MediaStore.AUTHORITY)) { 119 ((MediaProvider) cpc.getLocalContentProvider()).onPackageOrphaned(packageName); 120 } 121 } 122 onScanVolume(Context context, Uri uri)123 public static void onScanVolume(Context context, Uri uri) throws IOException { 124 final File file = new File(uri.getPath()).getCanonicalFile(); 125 final String volumeName = MediaStore.getVolumeName(file); 126 127 // If we're about to scan primary external storage, scan internal first 128 // to ensure that we have ringtones ready to roll before a possibly very 129 // long external storage scan 130 if (MediaStore.VOLUME_EXTERNAL_PRIMARY.equals(volumeName)) { 131 onScanVolume(context, Uri.fromFile(Environment.getRootDirectory())); 132 ensureDefaultRingtones(context); 133 } 134 135 try (ContentProviderClient cpc = context.getContentResolver() 136 .acquireContentProviderClient(MediaStore.AUTHORITY)) { 137 ((MediaProvider) cpc.getLocalContentProvider()).attachVolume(volumeName); 138 139 final ContentResolver resolver = ContentResolver.wrap(cpc.getLocalContentProvider()); 140 141 ContentValues values = new ContentValues(); 142 values.put(MediaStore.MEDIA_SCANNER_VOLUME, volumeName); 143 Uri scanUri = resolver.insert(MediaStore.getMediaScannerUri(), values); 144 145 if (!MediaStore.VOLUME_INTERNAL.equals(volumeName)) { 146 context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_STARTED, uri)); 147 } 148 149 for (File dir : resolveDirectories(volumeName)) { 150 MediaScanner.instance(context).scanDirectory(dir); 151 } 152 153 resolver.delete(scanUri, null, null); 154 155 } finally { 156 if (!MediaStore.VOLUME_INTERNAL.equals(volumeName)) { 157 context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_FINISHED, uri)); 158 } 159 } 160 } 161 onScanFile(Context context, Uri uri)162 public static Uri onScanFile(Context context, Uri uri) throws IOException { 163 final File file = new File(uri.getPath()).getCanonicalFile(); 164 return MediaScanner.instance(context).scanFile(file); 165 } 166 resolveDirectories(String volumeName)167 private static Collection<File> resolveDirectories(String volumeName) 168 throws FileNotFoundException { 169 return MediaStore.getVolumeScanPaths(volumeName); 170 } 171 172 /** 173 * Ensure that we've set ringtones at least once after initial scan. 174 */ ensureDefaultRingtones(Context context)175 private static void ensureDefaultRingtones(Context context) { 176 for (int type : new int[] { 177 TYPE_RINGTONE, 178 TYPE_NOTIFICATION, 179 TYPE_ALARM, 180 }) { 181 // Skip if we've already defined it at least once, so we don't 182 // overwrite the user changing to null 183 final String setting = getDefaultRingtoneSetting(type); 184 if (Settings.System.getInt(context.getContentResolver(), setting, 0) != 0) { 185 continue; 186 } 187 188 // Try finding the scanned ringtone 189 final String filename = getDefaultRingtoneFilename(type); 190 final Uri baseUri = MediaStore.Audio.Media.INTERNAL_CONTENT_URI; 191 try (Cursor cursor = context.getContentResolver().query(baseUri, 192 new String[] { MediaColumns._ID }, 193 MediaColumns.DISPLAY_NAME + "=?", 194 new String[] { filename }, null)) { 195 if (cursor.moveToFirst()) { 196 final Uri ringtoneUri = context.getContentResolver().canonicalizeOrElse( 197 ContentUris.withAppendedId(baseUri, cursor.getLong(0))); 198 RingtoneManager.setActualDefaultRingtoneUri(context, type, ringtoneUri); 199 Settings.System.putInt(context.getContentResolver(), setting, 1); 200 } 201 } 202 } 203 } 204 getDefaultRingtoneSetting(int type)205 private static String getDefaultRingtoneSetting(int type) { 206 switch (type) { 207 case TYPE_RINGTONE: return "ringtone_set"; 208 case TYPE_NOTIFICATION: return "notification_sound_set"; 209 case TYPE_ALARM: return "alarm_alert_set"; 210 default: throw new IllegalArgumentException(); 211 } 212 } 213 getDefaultRingtoneFilename(int type)214 private static String getDefaultRingtoneFilename(int type) { 215 switch (type) { 216 case TYPE_RINGTONE: return SystemProperties.get("ro.config.ringtone"); 217 case TYPE_NOTIFICATION: return SystemProperties.get("ro.config.notification_sound"); 218 case TYPE_ALARM: return SystemProperties.get("ro.config.alarm_alert"); 219 default: throw new IllegalArgumentException(); 220 } 221 } 222 } 223