1 /* 2 * Copyright (C) 2012 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; 18 19 import android.Manifest; 20 import android.annotation.TargetApi; 21 import android.app.Fragment; 22 import android.app.FragmentManager; 23 import android.app.FragmentTransaction; 24 import android.app.TimePickerDialog; 25 import android.content.Context; 26 import android.content.pm.PackageManager; 27 import android.media.RingtoneManager; 28 import android.net.Uri; 29 import android.os.Build; 30 import android.provider.Settings; 31 import android.support.annotation.VisibleForTesting; 32 import android.text.format.DateFormat; 33 import android.text.format.DateUtils; 34 import android.widget.Toast; 35 36 import com.android.deskclock.provider.Alarm; 37 import com.android.deskclock.provider.AlarmInstance; 38 39 import java.util.Calendar; 40 import java.util.Locale; 41 42 /** 43 * Static utility methods for Alarms. 44 */ 45 public class AlarmUtils { 46 public static final String FRAG_TAG_TIME_PICKER = "time_dialog"; 47 getFormattedTime(Context context, Calendar time)48 public static String getFormattedTime(Context context, Calendar time) { 49 String pattern; 50 if (Utils.isJBMR2OrLater()) { 51 final String skeleton = DateFormat.is24HourFormat(context) ? "EHm" : "Ehma"; 52 pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton); 53 return (String) DateFormat.format(pattern, time); 54 } else { 55 pattern = DateFormat.is24HourFormat(context) 56 ? context.getString(R.string.weekday_time_format_24_mode) 57 : context.getString(R.string.weekday_time_format_12_mode); 58 } 59 return (String) DateFormat.format(pattern, time); 60 } 61 getAlarmText(Context context, AlarmInstance instance)62 public static String getAlarmText(Context context, AlarmInstance instance) { 63 String alarmTimeStr = getFormattedTime(context, instance.getAlarmTime()); 64 return !instance.mLabel.isEmpty() ? alarmTimeStr + " - " + instance.mLabel 65 : alarmTimeStr; 66 } 67 68 // show time picker dialog for pre-L devices showTimeEditDialog(FragmentManager manager, final Alarm alarm, com.android.datetimepicker.time.TimePickerDialog.OnTimeSetListener listener, boolean is24HourMode)69 public static void showTimeEditDialog(FragmentManager manager, final Alarm alarm, 70 com.android.datetimepicker.time.TimePickerDialog.OnTimeSetListener listener, 71 boolean is24HourMode) { 72 73 final int hour, minutes; 74 if (alarm == null) { 75 hour = 0; 76 minutes = 0; 77 } else { 78 hour = alarm.hour; 79 minutes = alarm.minutes; 80 } 81 com.android.datetimepicker.time.TimePickerDialog dialog = 82 com.android.datetimepicker.time.TimePickerDialog.newInstance(listener, 83 hour, minutes, is24HourMode); 84 dialog.setThemeDark(true); 85 86 // Make sure the dialog isn't already added. 87 manager.executePendingTransactions(); 88 final FragmentTransaction ft = manager.beginTransaction(); 89 final Fragment prev = manager.findFragmentByTag(FRAG_TAG_TIME_PICKER); 90 if (prev != null) { 91 ft.remove(prev); 92 } 93 ft.commit(); 94 95 if (!dialog.isAdded()) { 96 dialog.show(manager, FRAG_TAG_TIME_PICKER); 97 } 98 } 99 100 /** 101 * Show the time picker dialog for post-L devices. 102 * This is called from AlarmClockFragment to set alarm. 103 * @param fragment The calling fragment (which is also a onTimeSetListener), 104 * we use it as the target fragment of the TimePickerFragment, so later the 105 * latter can retrieve it and set it as its onTimeSetListener when the fragment 106 * is recreated. 107 * @param alarm The clicked alarm, it can be null if user was clicking the fab instead. 108 */ 109 @TargetApi(Build.VERSION_CODES.LOLLIPOP) showTimeEditDialog(Fragment fragment, final Alarm alarm)110 public static void showTimeEditDialog(Fragment fragment, final Alarm alarm) { 111 final FragmentManager manager = fragment.getFragmentManager(); 112 final FragmentTransaction ft = manager.beginTransaction(); 113 final Fragment prev = manager.findFragmentByTag(FRAG_TAG_TIME_PICKER); 114 if (prev != null) { 115 ft.remove(prev); 116 } 117 ft.commit(); 118 final TimePickerFragment timePickerFragment = new TimePickerFragment(); 119 timePickerFragment.setTargetFragment(fragment, 0); 120 timePickerFragment.setOnTimeSetListener((TimePickerDialog.OnTimeSetListener) fragment); 121 timePickerFragment.setAlarm(alarm); 122 timePickerFragment.show(manager, FRAG_TAG_TIME_PICKER); 123 } 124 125 /** 126 * @return {@code true} iff the user has granted permission to read the ringtone at the given 127 * uri or no permission is required to read the ringtone 128 */ hasPermissionToDisplayRingtoneTitle(Context context, Uri ringtoneUri)129 public static boolean hasPermissionToDisplayRingtoneTitle(Context context, Uri ringtoneUri) { 130 final PackageManager pm = context.getPackageManager(); 131 final String packageName = context.getPackageName(); 132 133 // If the default alarm alert ringtone URI is given, resolve it to the actual URI. 134 if (Settings.System.DEFAULT_ALARM_ALERT_URI.equals(ringtoneUri)) { 135 ringtoneUri = RingtoneManager.getActualDefaultRingtoneUri(context, 136 RingtoneManager.TYPE_ALARM); 137 } 138 139 // If no ringtone is specified, return true. 140 if (ringtoneUri == null || ringtoneUri == Alarm.NO_RINGTONE_URI) { 141 return true; 142 } 143 144 // If the permission is already granted, return true. 145 if (pm.checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE, packageName) 146 == PackageManager.PERMISSION_GRANTED) { 147 return true; 148 } 149 150 // If the ringtone is internal, return true; 151 // external ringtones require the permission to see their title 152 return ringtoneUri.toString().startsWith("content://media/internal/"); 153 } 154 155 /** 156 * format "Alarm set for 2 days, 7 hours, and 53 minutes from now." 157 */ 158 @VisibleForTesting formatElapsedTimeUntilAlarm(Context context, long delta)159 static String formatElapsedTimeUntilAlarm(Context context, long delta) { 160 // If the alarm will ring within 60 seconds, just report "less than a minute." 161 final String[] formats = context.getResources().getStringArray(R.array.alarm_set); 162 if (delta < DateUtils.MINUTE_IN_MILLIS) { 163 return formats[0]; 164 } 165 166 // Otherwise, format the remaining time until the alarm rings. 167 168 // Round delta upwards to the nearest whole minute. (e.g. 7m 58s -> 8m) 169 final long remainder = delta % DateUtils.MINUTE_IN_MILLIS; 170 delta += remainder == 0 ? 0 : (DateUtils.MINUTE_IN_MILLIS - remainder); 171 172 int hours = (int) delta / (1000 * 60 * 60); 173 final int minutes = (int) delta / (1000 * 60) % 60; 174 final int days = hours / 24; 175 hours = hours % 24; 176 177 String daySeq = Utils.getNumberFormattedQuantityString(context, R.plurals.days, days); 178 String minSeq = Utils.getNumberFormattedQuantityString(context, R.plurals.minutes, minutes); 179 String hourSeq = Utils.getNumberFormattedQuantityString(context, R.plurals.hours, hours); 180 181 final boolean showDays = days > 0; 182 final boolean showHours = hours > 0; 183 final boolean showMinutes = minutes > 0; 184 185 // Compute the index of the most appropriate time format based on the time delta. 186 final int index = (showDays ? 1 : 0) | (showHours ? 2 : 0) | (showMinutes ? 4 : 0); 187 188 return String.format(formats[index], daySeq, hourSeq, minSeq); 189 } 190 popAlarmSetToast(Context context, long alarmTime)191 public static void popAlarmSetToast(Context context, long alarmTime) { 192 final long alarmTimeDelta = alarmTime - System.currentTimeMillis(); 193 final String text = formatElapsedTimeUntilAlarm(context, alarmTimeDelta); 194 Toast toast = Toast.makeText(context, text, Toast.LENGTH_LONG); 195 ToastMaster.setToast(toast); 196 toast.show(); 197 } 198 } 199