blob: caf552304b9a545fed79dc486fe85f0a6efc2d1a [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.providers.media;
import static android.media.RingtoneManager.TYPE_ALARM;
import static android.media.RingtoneManager.TYPE_NOTIFICATION;
import static android.media.RingtoneManager.TYPE_RINGTONE;
import static com.android.providers.media.MediaProvider.TAG;
import android.app.IntentService;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Environment;
import android.os.PowerManager;
import android.os.SystemProperties;
import android.os.Trace;
import android.provider.MediaStore;
import android.provider.MediaStore.MediaColumns;
import android.provider.Settings;
import android.util.Log;
import com.android.providers.media.scan.MediaScanner;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Collection;
public class MediaService extends IntentService {
public MediaService() {
super(TAG);
}
private PowerManager.WakeLock mWakeLock;
@Override
public void onCreate() {
super.onCreate();
mWakeLock = getSystemService(PowerManager.class).newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, TAG);
}
@Override
protected void onHandleIntent(Intent intent) {
mWakeLock.acquire();
Trace.traceBegin(Trace.TRACE_TAG_DATABASE, intent.getAction());
if (Log.isLoggable(TAG, Log.INFO)) {
Log.i(TAG, "Begin " + intent);
}
try {
switch (intent.getAction()) {
case Intent.ACTION_LOCALE_CHANGED: {
onLocaleChanged();
break;
}
case Intent.ACTION_PACKAGE_FULLY_REMOVED:
case Intent.ACTION_PACKAGE_DATA_CLEARED: {
final String packageName = intent.getData().getSchemeSpecificPart();
onPackageOrphaned(packageName);
break;
}
case Intent.ACTION_MEDIA_MOUNTED: {
onScanVolume(this, intent.getData());
break;
}
case Intent.ACTION_MEDIA_SCANNER_SCAN_FILE: {
onScanFile(this, intent.getData());
break;
}
default: {
Log.w(TAG, "Unknown intent " + intent);
break;
}
}
} catch (Exception e) {
Log.w(TAG, "Failed operation " + intent, e);
} finally {
if (Log.isLoggable(TAG, Log.INFO)) {
Log.i(TAG, "End " + intent);
}
Trace.traceEnd(Trace.TRACE_TAG_DATABASE);
mWakeLock.release();
}
}
private void onLocaleChanged() {
try (ContentProviderClient cpc = getContentResolver()
.acquireContentProviderClient(MediaStore.AUTHORITY)) {
((MediaProvider) cpc.getLocalContentProvider()).onLocaleChanged();
}
}
private void onPackageOrphaned(String packageName) {
try (ContentProviderClient cpc = getContentResolver()
.acquireContentProviderClient(MediaStore.AUTHORITY)) {
((MediaProvider) cpc.getLocalContentProvider()).onPackageOrphaned(packageName);
}
}
public static void onScanVolume(Context context, Uri uri) throws IOException {
final File file = new File(uri.getPath()).getCanonicalFile();
final String volumeName = MediaStore.getVolumeName(file);
// If we're about to scan primary external storage, scan internal first
// to ensure that we have ringtones ready to roll before a possibly very
// long external storage scan
if (MediaStore.VOLUME_EXTERNAL_PRIMARY.equals(volumeName)) {
onScanVolume(context, Uri.fromFile(Environment.getRootDirectory()));
ensureDefaultRingtones(context);
}
try (ContentProviderClient cpc = context.getContentResolver()
.acquireContentProviderClient(MediaStore.AUTHORITY)) {
((MediaProvider) cpc.getLocalContentProvider()).attachVolume(volumeName);
final ContentResolver resolver = ContentResolver.wrap(cpc.getLocalContentProvider());
ContentValues values = new ContentValues();
values.put(MediaStore.MEDIA_SCANNER_VOLUME, volumeName);
Uri scanUri = resolver.insert(MediaStore.getMediaScannerUri(), values);
if (!MediaStore.VOLUME_INTERNAL.equals(volumeName)) {
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_STARTED, uri));
}
for (File dir : resolveDirectories(volumeName)) {
MediaScanner.instance(context).scanDirectory(dir);
}
resolver.delete(scanUri, null, null);
} finally {
if (!MediaStore.VOLUME_INTERNAL.equals(volumeName)) {
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_FINISHED, uri));
}
}
}
public static Uri onScanFile(Context context, Uri uri) throws IOException {
final File file = new File(uri.getPath()).getCanonicalFile();
return MediaScanner.instance(context).scanFile(file);
}
private static Collection<File> resolveDirectories(String volumeName)
throws FileNotFoundException {
return MediaStore.getVolumeScanPaths(volumeName);
}
/**
* Ensure that we've set ringtones at least once after initial scan.
*/
private static void ensureDefaultRingtones(Context context) {
for (int type : new int[] {
TYPE_RINGTONE,
TYPE_NOTIFICATION,
TYPE_ALARM,
}) {
// Skip if we've already defined it at least once, so we don't
// overwrite the user changing to null
final String setting = getDefaultRingtoneSetting(type);
if (Settings.System.getInt(context.getContentResolver(), setting, 0) != 0) {
continue;
}
// Try finding the scanned ringtone
final String filename = getDefaultRingtoneFilename(type);
final Uri baseUri = MediaStore.Audio.Media.INTERNAL_CONTENT_URI;
try (Cursor cursor = context.getContentResolver().query(baseUri,
new String[] { MediaColumns._ID },
MediaColumns.DISPLAY_NAME + "=?",
new String[] { filename }, null)) {
if (cursor.moveToFirst()) {
final Uri ringtoneUri = context.getContentResolver().canonicalizeOrElse(
ContentUris.withAppendedId(baseUri, cursor.getLong(0)));
RingtoneManager.setActualDefaultRingtoneUri(context, type, ringtoneUri);
Settings.System.putInt(context.getContentResolver(), setting, 1);
}
}
}
}
private static String getDefaultRingtoneSetting(int type) {
switch (type) {
case TYPE_RINGTONE: return "ringtone_set";
case TYPE_NOTIFICATION: return "notification_sound_set";
case TYPE_ALARM: return "alarm_alert_set";
default: throw new IllegalArgumentException();
}
}
private static String getDefaultRingtoneFilename(int type) {
switch (type) {
case TYPE_RINGTONE: return SystemProperties.get("ro.config.ringtone");
case TYPE_NOTIFICATION: return SystemProperties.get("ro.config.notification_sound");
case TYPE_ALARM: return SystemProperties.get("ro.config.alarm_alert");
default: throw new IllegalArgumentException();
}
}
}