blob: 90b7f912c1460c5f2d4a659d7131e94a1ac0fb1e [file] [log] [blame]
/*
* Copyright (C) 2016 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.deskclock.data;
import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.UriPermission;
import android.database.ContentObserver;
import android.database.Cursor;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Handler;
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.ArraySet;
import com.android.deskclock.LogUtils;
import com.android.deskclock.R;
import com.android.deskclock.provider.Alarm;
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import static android.media.AudioManager.STREAM_ALARM;
import static android.media.RingtoneManager.TITLE_COLUMN_INDEX;
/**
* All ringtone data is accessed via this model.
*/
final class RingtoneModel {
private final Context mContext;
private final SharedPreferences mPrefs;
/** Maps ringtone uri to ringtone title; looking up a title from scratch is expensive. */
private final Map<Uri, String> mRingtoneTitles = new ArrayMap<>(16);
/** Clears data structures containing data that is locale-sensitive. */
@SuppressWarnings("FieldCanBeLocal")
private final BroadcastReceiver mLocaleChangedReceiver = new LocaleChangedReceiver();
/** A mutable copy of the custom ringtones. */
private List<CustomRingtone> mCustomRingtones;
RingtoneModel(Context context, SharedPreferences prefs) {
mContext = context;
mPrefs = prefs;
// Clear caches affected by system settings when system settings change.
final ContentResolver cr = mContext.getContentResolver();
final ContentObserver observer = new SystemAlarmAlertChangeObserver();
cr.registerContentObserver(Settings.System.DEFAULT_ALARM_ALERT_URI, false, observer);
// Clear caches affected by locale when locale changes.
final IntentFilter localeBroadcastFilter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
mContext.registerReceiver(mLocaleChangedReceiver, localeBroadcastFilter);
}
CustomRingtone addCustomRingtone(Uri uri, String title) {
// If the uri is already present in an existing ringtone, do nothing.
final CustomRingtone existing = getCustomRingtone(uri);
if (existing != null) {
return existing;
}
final CustomRingtone ringtone = CustomRingtoneDAO.addCustomRingtone(mPrefs, uri, title);
getMutableCustomRingtones().add(ringtone);
Collections.sort(getMutableCustomRingtones());
return ringtone;
}
void removeCustomRingtone(Uri uri) {
final List<CustomRingtone> ringtones = getMutableCustomRingtones();
for (CustomRingtone ringtone : ringtones) {
if (ringtone.getUri().equals(uri)) {
CustomRingtoneDAO.removeCustomRingtone(mPrefs, ringtone.getId());
ringtones.remove(ringtone);
break;
}
}
}
private CustomRingtone getCustomRingtone(Uri uri) {
for (CustomRingtone ringtone : getMutableCustomRingtones()) {
if (ringtone.getUri().equals(uri)) {
return ringtone;
}
}
return null;
}
List<CustomRingtone> getCustomRingtones() {
return Collections.unmodifiableList(getMutableCustomRingtones());
}
@SuppressLint("NewApi")
void loadRingtonePermissions() {
final List<CustomRingtone> ringtones = getMutableCustomRingtones();
if (ringtones.isEmpty()) {
return;
}
final List<UriPermission> uriPermissions =
mContext.getContentResolver().getPersistedUriPermissions();
final Set<Uri> permissions = new ArraySet<>(uriPermissions.size());
for (UriPermission uriPermission : uriPermissions) {
permissions.add(uriPermission.getUri());
}
for (ListIterator<CustomRingtone> i = ringtones.listIterator(); i.hasNext();) {
final CustomRingtone ringtone = i.next();
i.set(ringtone.setHasPermissions(permissions.contains(ringtone.getUri())));
}
}
void loadRingtoneTitles() {
// Early return if the cache is already primed.
if (!mRingtoneTitles.isEmpty()) {
return;
}
final RingtoneManager ringtoneManager = new RingtoneManager(mContext);
ringtoneManager.setType(STREAM_ALARM);
// Cache a title for each system ringtone.
try (Cursor cursor = ringtoneManager.getCursor()) {
for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
final String ringtoneTitle = cursor.getString(TITLE_COLUMN_INDEX);
final Uri ringtoneUri = ringtoneManager.getRingtoneUri(cursor.getPosition());
mRingtoneTitles.put(ringtoneUri, ringtoneTitle);
}
} catch (Throwable ignored) {
// best attempt only
LogUtils.e("Error loading ringtone title cache", ignored);
}
}
String getRingtoneTitle(Uri uri) {
// Special case: no ringtone has a title of "Silent".
if (Alarm.NO_RINGTONE_URI.equals(uri)) {
return mContext.getString(R.string.silent_ringtone_title);
}
// If the ringtone is custom, it has its own title.
final CustomRingtone customRingtone = getCustomRingtone(uri);
if (customRingtone != null) {
return customRingtone.getTitle();
}
// Check the cache.
String title = mRingtoneTitles.get(uri);
if (title == null) {
// This is slow because a media player is created during Ringtone object creation.
final Ringtone ringtone = RingtoneManager.getRingtone(mContext, uri);
if (ringtone == null) {
LogUtils.e("No ringtone for uri: %s", uri);
return mContext.getString(R.string.unknown_ringtone_title);
}
// Cache the title for later use.
title = ringtone.getTitle(mContext);
mRingtoneTitles.put(uri, title);
}
return title;
}
private List<CustomRingtone> getMutableCustomRingtones() {
if (mCustomRingtones == null) {
mCustomRingtones = CustomRingtoneDAO.getCustomRingtones(mPrefs);
Collections.sort(mCustomRingtones);
}
return mCustomRingtones;
}
/**
* This receiver is notified when system settings change. Cached information built on
* those system settings must be cleared.
*/
private final class SystemAlarmAlertChangeObserver extends ContentObserver {
private SystemAlarmAlertChangeObserver() {
super(new Handler());
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
// Titles such as "Default ringtone (Oxygen)" are wrong after default ringtone changes.
mRingtoneTitles.clear();
}
}
/**
* Cached information that is locale-sensitive must be cleared in response to locale changes.
*/
private final class LocaleChangedReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// Titles such as "Default ringtone (Oxygen)" are wrong after locale changes.
mRingtoneTitles.clear();
}
}
}