Home | History | Annotate | Download | only in data
      1 /*
      2  * Copyright (C) 2016 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.deskclock.data;
     18 
     19 import android.annotation.SuppressLint;
     20 import android.content.BroadcastReceiver;
     21 import android.content.ContentResolver;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.IntentFilter;
     25 import android.content.SharedPreferences;
     26 import android.content.UriPermission;
     27 import android.database.ContentObserver;
     28 import android.database.Cursor;
     29 import android.media.Ringtone;
     30 import android.media.RingtoneManager;
     31 import android.net.Uri;
     32 import android.os.Handler;
     33 import android.provider.Settings;
     34 import android.util.ArrayMap;
     35 import android.util.ArraySet;
     36 
     37 import com.android.deskclock.LogUtils;
     38 import com.android.deskclock.R;
     39 import com.android.deskclock.provider.Alarm;
     40 
     41 import java.util.Collections;
     42 import java.util.List;
     43 import java.util.ListIterator;
     44 import java.util.Map;
     45 import java.util.Set;
     46 
     47 import static android.media.AudioManager.STREAM_ALARM;
     48 import static android.media.RingtoneManager.TITLE_COLUMN_INDEX;
     49 
     50 /**
     51  * All ringtone data is accessed via this model.
     52  */
     53 final class RingtoneModel {
     54 
     55     private final Context mContext;
     56 
     57     private final SharedPreferences mPrefs;
     58 
     59     /** Maps ringtone uri to ringtone title; looking up a title from scratch is expensive. */
     60     private final Map<Uri, String> mRingtoneTitles = new ArrayMap<>(16);
     61 
     62     /** Clears data structures containing data that is locale-sensitive. */
     63     @SuppressWarnings("FieldCanBeLocal")
     64     private final BroadcastReceiver mLocaleChangedReceiver = new LocaleChangedReceiver();
     65 
     66     /** A mutable copy of the custom ringtones. */
     67     private List<CustomRingtone> mCustomRingtones;
     68 
     69     RingtoneModel(Context context, SharedPreferences prefs) {
     70         mContext = context;
     71         mPrefs = prefs;
     72 
     73         // Clear caches affected by system settings when system settings change.
     74         final ContentResolver cr = mContext.getContentResolver();
     75         final ContentObserver observer = new SystemAlarmAlertChangeObserver();
     76         cr.registerContentObserver(Settings.System.DEFAULT_ALARM_ALERT_URI, false, observer);
     77 
     78         // Clear caches affected by locale when locale changes.
     79         final IntentFilter localeBroadcastFilter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
     80         mContext.registerReceiver(mLocaleChangedReceiver, localeBroadcastFilter);
     81     }
     82 
     83     CustomRingtone addCustomRingtone(Uri uri, String title) {
     84         // If the uri is already present in an existing ringtone, do nothing.
     85         final CustomRingtone existing = getCustomRingtone(uri);
     86         if (existing != null) {
     87             return existing;
     88         }
     89 
     90         final CustomRingtone ringtone = CustomRingtoneDAO.addCustomRingtone(mPrefs, uri, title);
     91         getMutableCustomRingtones().add(ringtone);
     92         Collections.sort(getMutableCustomRingtones());
     93         return ringtone;
     94     }
     95 
     96     void removeCustomRingtone(Uri uri) {
     97         final List<CustomRingtone> ringtones = getMutableCustomRingtones();
     98         for (CustomRingtone ringtone : ringtones) {
     99             if (ringtone.getUri().equals(uri)) {
    100                 CustomRingtoneDAO.removeCustomRingtone(mPrefs, ringtone.getId());
    101                 ringtones.remove(ringtone);
    102                 break;
    103             }
    104         }
    105     }
    106 
    107     private CustomRingtone getCustomRingtone(Uri uri) {
    108         for (CustomRingtone ringtone : getMutableCustomRingtones()) {
    109             if (ringtone.getUri().equals(uri)) {
    110                 return ringtone;
    111             }
    112         }
    113 
    114         return null;
    115     }
    116 
    117     List<CustomRingtone> getCustomRingtones() {
    118         return Collections.unmodifiableList(getMutableCustomRingtones());
    119     }
    120 
    121     @SuppressLint("NewApi")
    122     void loadRingtonePermissions() {
    123         final List<CustomRingtone> ringtones = getMutableCustomRingtones();
    124         if (ringtones.isEmpty()) {
    125             return;
    126         }
    127 
    128         final List<UriPermission> uriPermissions =
    129                 mContext.getContentResolver().getPersistedUriPermissions();
    130         final Set<Uri> permissions = new ArraySet<>(uriPermissions.size());
    131         for (UriPermission uriPermission : uriPermissions) {
    132             permissions.add(uriPermission.getUri());
    133         }
    134 
    135         for (ListIterator<CustomRingtone> i = ringtones.listIterator(); i.hasNext();) {
    136             final CustomRingtone ringtone = i.next();
    137             i.set(ringtone.setHasPermissions(permissions.contains(ringtone.getUri())));
    138         }
    139     }
    140 
    141     void loadRingtoneTitles() {
    142         // Early return if the cache is already primed.
    143         if (!mRingtoneTitles.isEmpty()) {
    144             return;
    145         }
    146 
    147         final RingtoneManager ringtoneManager = new RingtoneManager(mContext);
    148         ringtoneManager.setType(STREAM_ALARM);
    149 
    150         // Cache a title for each system ringtone.
    151         try (Cursor cursor = ringtoneManager.getCursor()) {
    152             for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
    153                 final String ringtoneTitle = cursor.getString(TITLE_COLUMN_INDEX);
    154                 final Uri ringtoneUri = ringtoneManager.getRingtoneUri(cursor.getPosition());
    155                 mRingtoneTitles.put(ringtoneUri, ringtoneTitle);
    156             }
    157         } catch (Throwable ignored) {
    158             // best attempt only
    159             LogUtils.e("Error loading ringtone title cache", ignored);
    160         }
    161     }
    162 
    163     String getRingtoneTitle(Uri uri) {
    164         // Special case: no ringtone has a title of "Silent".
    165         if (Alarm.NO_RINGTONE_URI.equals(uri)) {
    166             return mContext.getString(R.string.silent_ringtone_title);
    167         }
    168 
    169         // If the ringtone is custom, it has its own title.
    170         final CustomRingtone customRingtone = getCustomRingtone(uri);
    171         if (customRingtone != null) {
    172             return customRingtone.getTitle();
    173         }
    174 
    175         // Check the cache.
    176         String title = mRingtoneTitles.get(uri);
    177 
    178         if (title == null) {
    179             // This is slow because a media player is created during Ringtone object creation.
    180             final Ringtone ringtone = RingtoneManager.getRingtone(mContext, uri);
    181             if (ringtone == null) {
    182                 LogUtils.e("No ringtone for uri: %s", uri);
    183                 return mContext.getString(R.string.unknown_ringtone_title);
    184             }
    185 
    186             // Cache the title for later use.
    187             title = ringtone.getTitle(mContext);
    188             mRingtoneTitles.put(uri, title);
    189         }
    190         return title;
    191     }
    192 
    193     private List<CustomRingtone> getMutableCustomRingtones() {
    194         if (mCustomRingtones == null) {
    195             mCustomRingtones = CustomRingtoneDAO.getCustomRingtones(mPrefs);
    196             Collections.sort(mCustomRingtones);
    197         }
    198 
    199         return mCustomRingtones;
    200     }
    201 
    202     /**
    203      * This receiver is notified when system settings change. Cached information built on
    204      * those system settings must be cleared.
    205      */
    206     private final class SystemAlarmAlertChangeObserver extends ContentObserver {
    207 
    208         private SystemAlarmAlertChangeObserver() {
    209             super(new Handler());
    210         }
    211 
    212         @Override
    213         public void onChange(boolean selfChange) {
    214             super.onChange(selfChange);
    215 
    216             // Titles such as "Default ringtone (Oxygen)" are wrong after default ringtone changes.
    217             mRingtoneTitles.clear();
    218         }
    219     }
    220 
    221     /**
    222      * Cached information that is locale-sensitive must be cleared in response to locale changes.
    223      */
    224     private final class LocaleChangedReceiver extends BroadcastReceiver {
    225         @Override
    226         public void onReceive(Context context, Intent intent) {
    227             // Titles such as "Default ringtone (Oxygen)" are wrong after locale changes.
    228             mRingtoneTitles.clear();
    229         }
    230     }
    231 }