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