1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17 package com.android.alarmclock; 18 19 import android.app.AlarmManager; 20 import android.app.PendingIntent; 21 import android.appwidget.AppWidgetManager; 22 import android.appwidget.AppWidgetProvider; 23 import android.appwidget.AppWidgetProviderInfo; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.net.Uri; 28 import android.os.Bundle; 29 import android.support.annotation.NonNull; 30 import android.text.TextUtils; 31 import android.text.format.DateFormat; 32 import android.util.Log; 33 import android.util.TypedValue; 34 import android.view.View; 35 import android.widget.RemoteViews; 36 37 import com.android.deskclock.HandleDeskClockApiCalls; 38 import com.android.deskclock.R; 39 import com.android.deskclock.Utils; 40 import com.android.deskclock.alarms.AlarmStateManager; 41 import com.android.deskclock.data.DataModel; 42 import com.android.deskclock.worldclock.CitySelectionActivity; 43 44 import java.util.Locale; 45 46 public class DigitalAppWidgetProvider extends AppWidgetProvider { 47 private static final String TAG = "DigAppWidgetProvider"; 48 49 /** 50 * Intent to be used for checking if a world clock's date has changed. Must be every fifteen 51 * minutes because not all time zones are hour-locked. 52 **/ 53 public static final String ACTION_ON_QUARTER_HOUR = "com.android.deskclock.ON_QUARTER_HOUR"; 54 55 // Lazily creating this intent to use with the AlarmManager 56 private PendingIntent mPendingIntent; 57 // Lazily creating this name to use with the AppWidgetManager 58 private ComponentName mComponentName; 59 DigitalAppWidgetProvider()60 public DigitalAppWidgetProvider() { 61 } 62 63 @Override onEnabled(Context context)64 public void onEnabled(Context context) { 65 super.onEnabled(context); 66 startAlarmOnQuarterHour(context); 67 } 68 69 @Override onDisabled(Context context)70 public void onDisabled(Context context) { 71 super.onDisabled(context); 72 cancelAlarmOnQuarterHour(context); 73 } 74 75 @Override onReceive(@onNull Context context, @NonNull Intent intent)76 public void onReceive(@NonNull Context context, @NonNull Intent intent) { 77 String action = intent.getAction(); 78 if (DigitalAppWidgetService.LOGGING) { 79 Log.i(TAG, "onReceive: " + action); 80 } 81 super.onReceive(context, intent); 82 83 if (ACTION_ON_QUARTER_HOUR.equals(action) 84 || Intent.ACTION_DATE_CHANGED.equals(action) 85 || Intent.ACTION_TIMEZONE_CHANGED.equals(action) 86 || Intent.ACTION_TIME_CHANGED.equals(action) 87 || Intent.ACTION_LOCALE_CHANGED.equals(action)) { 88 AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); 89 if (appWidgetManager != null) { 90 int[] appWidgetIds = appWidgetManager.getAppWidgetIds(getComponentName(context)); 91 for (int appWidgetId : appWidgetIds) { 92 appWidgetManager. 93 notifyAppWidgetViewDataChanged(appWidgetId, 94 R.id.digital_appwidget_listview); 95 RemoteViews widget = new RemoteViews(context.getPackageName(), 96 R.layout.digital_appwidget); 97 float ratio = WidgetUtils.getScaleRatio(context, null, appWidgetId); 98 WidgetUtils.setTimeFormat(context, widget, false /* showAmPm */, 99 R.id.the_clock); 100 WidgetUtils.setClockSize(context, widget, ratio); 101 refreshAlarm(context, widget, ratio); 102 appWidgetManager.partiallyUpdateAppWidget(appWidgetId, widget); 103 } 104 } 105 if(!ACTION_ON_QUARTER_HOUR.equals(action)) { 106 cancelAlarmOnQuarterHour(context); 107 } 108 startAlarmOnQuarterHour(context); 109 } else if (isNextAlarmChangedAction(action) 110 || Intent.ACTION_SCREEN_ON.equals(action) 111 || DataModel.ACTION_DIGITAL_WIDGET_CHANGED.equals(action)) { 112 AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); 113 if (appWidgetManager != null) { 114 int[] appWidgetIds = appWidgetManager.getAppWidgetIds(getComponentName(context)); 115 for (int appWidgetId : appWidgetIds) { 116 final float ratio = WidgetUtils.getScaleRatio(context, null, appWidgetId); 117 updateClock(context, appWidgetManager, appWidgetId, ratio); 118 } 119 } 120 } 121 } 122 123 @Override onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)124 public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { 125 if (DigitalAppWidgetService.LOGGING) { 126 Log.i(TAG, "onUpdate"); 127 } 128 for (int appWidgetId : appWidgetIds) { 129 float ratio = WidgetUtils.getScaleRatio(context, null, appWidgetId); 130 updateClock(context, appWidgetManager, appWidgetId, ratio); 131 } 132 startAlarmOnQuarterHour(context); 133 super.onUpdate(context, appWidgetManager, appWidgetIds); 134 } 135 136 @Override onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions)137 public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, 138 int appWidgetId, Bundle newOptions) { 139 // scale the fonts of the clock to fit inside the new size 140 float ratio = WidgetUtils.getScaleRatio(context, newOptions, appWidgetId); 141 AppWidgetManager widgetManager = AppWidgetManager.getInstance(context); 142 updateClock(context, widgetManager, appWidgetId, ratio); 143 } 144 145 /** 146 * Determine whether action received corresponds to a "next alarm" changed action depending 147 * on the SDK version. 148 */ isNextAlarmChangedAction(String action)149 private boolean isNextAlarmChangedAction(String action) { 150 final String nextAlarmIntentAction; 151 if (Utils.isLOrLater()) { 152 nextAlarmIntentAction = AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED; 153 } else { 154 nextAlarmIntentAction = AlarmStateManager.SYSTEM_ALARM_CHANGE_ACTION; 155 } 156 return nextAlarmIntentAction.equals(action); 157 } 158 updateClock( Context context, AppWidgetManager appWidgetManager, int appWidgetId, float ratio)159 private void updateClock( 160 Context context, AppWidgetManager appWidgetManager, int appWidgetId, float ratio) { 161 RemoteViews widget = new RemoteViews(context.getPackageName(), R.layout.digital_appwidget); 162 163 // Launch clock when clicking on the time in the widget only if not a lock screen widget 164 Bundle newOptions = appWidgetManager.getAppWidgetOptions(appWidgetId); 165 if (newOptions != null && 166 newOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY, -1) 167 != AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD) { 168 final Intent showClock = new Intent(HandleDeskClockApiCalls.ACTION_SHOW_CLOCK) 169 .putExtra(HandleDeskClockApiCalls.EXTRA_FROM_WIDGET, true); 170 final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, showClock, 0); 171 widget.setOnClickPendingIntent(R.id.digital_appwidget, pendingIntent); 172 } 173 174 // Setup formats and font sizes 175 refreshDate(context, widget, ratio); 176 refreshAlarm(context, widget, ratio); 177 WidgetUtils.setTimeFormat(context, widget, false /* showAmPm */, R.id.the_clock); 178 WidgetUtils.setClockSize(context, widget, ratio); 179 180 // Set up R.id.digital_appwidget_listview to use a remote views adapter 181 // That remote views adapter connects to a RemoteViewsService through intent. 182 final Intent intent = new Intent(context, DigitalAppWidgetService.class); 183 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); 184 intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME))); 185 widget.setRemoteAdapter(R.id.digital_appwidget_listview, intent); 186 187 // Set up the click on any world clock to start the Cities Activity 188 //TODO: Should this be in the options guard above? 189 final Intent selectCitiesIntent = new Intent(context, CitySelectionActivity.class); 190 widget.setPendingIntentTemplate(R.id.digital_appwidget_listview, 191 PendingIntent.getActivity(context, 0, selectCitiesIntent, 0)); 192 193 // Refresh the widget 194 appWidgetManager.notifyAppWidgetViewDataChanged( 195 appWidgetId, R.id.digital_appwidget_listview); 196 appWidgetManager.updateAppWidget(appWidgetId, widget); 197 } 198 refreshDate(Context context, RemoteViews widget, float ratio)199 private void refreshDate(Context context, RemoteViews widget, float ratio) { 200 if (ratio < 1) { 201 // The time text normally has a negative bottom margin to reduce the space between the 202 // time and the date. When we scale down they overlap, so give the date a positive 203 // top padding. 204 final float padding = (1 - ratio) * 205 -context.getResources().getDimension(R.dimen.bottom_text_spacing_digital); 206 widget.setViewPadding(R.id.date_and_alarm, 0, (int) padding, 0, 0); 207 } 208 209 // Set today's date format 210 final Locale locale = Locale.getDefault(); 211 final String skeleton = context.getString(R.string.abbrev_wday_abbrev_month_day_no_year); 212 final CharSequence timeFormat = DateFormat.getBestDateTimePattern(locale, skeleton); 213 widget.setCharSequence(R.id.date, "setFormat12Hour", timeFormat); 214 widget.setCharSequence(R.id.date, "setFormat24Hour", timeFormat); 215 final float fontSize = context.getResources().getDimension(R.dimen.widget_label_font_size); 216 widget.setTextViewTextSize(R.id.date, TypedValue.COMPLEX_UNIT_PX, fontSize * ratio); 217 } 218 refreshAlarm(Context context, RemoteViews widget, float ratio)219 protected void refreshAlarm(Context context, RemoteViews widget, float ratio) { 220 final String nextAlarm = Utils.getNextAlarm(context); 221 if (!TextUtils.isEmpty(nextAlarm)) { 222 final float fontSize = 223 context.getResources().getDimension(R.dimen.widget_label_font_size); 224 widget.setTextViewTextSize( 225 R.id.nextAlarm, TypedValue.COMPLEX_UNIT_PX, fontSize * ratio); 226 227 int alarmDrawableResId; 228 if (ratio < .72f) { 229 alarmDrawableResId = R.drawable.ic_alarm_small_12dp; 230 } 231 else if (ratio < .95f) { 232 alarmDrawableResId = R.drawable.ic_alarm_small_18dp; 233 } 234 else { 235 alarmDrawableResId = R.drawable.ic_alarm_small_24dp; 236 } 237 widget.setTextViewCompoundDrawablesRelative( 238 R.id.nextAlarm, alarmDrawableResId, 0, 0, 0); 239 240 widget.setTextViewText(R.id.nextAlarm, nextAlarm); 241 widget.setViewVisibility(R.id.nextAlarm, View.VISIBLE); 242 if (DigitalAppWidgetService.LOGGING) { 243 Log.v(TAG, "DigitalWidget sets next alarm string to " + nextAlarm); 244 } 245 } else { 246 widget.setViewVisibility(R.id.nextAlarm, View.GONE); 247 if (DigitalAppWidgetService.LOGGING) { 248 Log.v(TAG, "DigitalWidget sets next alarm string to null"); 249 } 250 } 251 } 252 253 /** 254 * Start an alarm that fires on the next quarter hour to update the world clock city 255 * day when the local time or the world city crosses midnight. 256 * 257 * @param context The context in which the PendingIntent should perform the broadcast. 258 */ startAlarmOnQuarterHour(Context context)259 private void startAlarmOnQuarterHour(Context context) { 260 if (context != null) { 261 final long onQuarterHour = Utils.getAlarmOnQuarterHour(); 262 final PendingIntent quarterlyIntent = getOnQuarterHourPendingIntent(context); 263 final AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); 264 am.setExact(AlarmManager.RTC, onQuarterHour, quarterlyIntent); 265 if (DigitalAppWidgetService.LOGGING) { 266 Log.v(TAG, "startAlarmOnQuarterHour " + context.toString()); 267 } 268 } 269 } 270 271 272 /** 273 * Remove the alarm for the quarter hour update. 274 * 275 * @param context The context in which the PendingIntent was started to perform the broadcast. 276 */ cancelAlarmOnQuarterHour(Context context)277 public void cancelAlarmOnQuarterHour(Context context) { 278 if (context != null) { 279 PendingIntent quarterlyIntent = getOnQuarterHourPendingIntent(context); 280 if (DigitalAppWidgetService.LOGGING) { 281 Log.v(TAG, "cancelAlarmOnQuarterHour " + context.toString()); 282 } 283 ((AlarmManager) context.getSystemService(Context.ALARM_SERVICE)).cancel( 284 quarterlyIntent); 285 } 286 } 287 288 /** 289 * Create the pending intent that is broadcast on the quarter hour. 290 * 291 * @param context The Context in which this PendingIntent should perform the broadcast. 292 * @return a pending intent with an intent unique to DigitalAppWidgetProvider 293 */ getOnQuarterHourPendingIntent(Context context)294 private PendingIntent getOnQuarterHourPendingIntent(Context context) { 295 if (mPendingIntent == null) { 296 mPendingIntent = PendingIntent.getBroadcast(context, 0, 297 new Intent(ACTION_ON_QUARTER_HOUR), PendingIntent.FLAG_CANCEL_CURRENT); 298 } 299 return mPendingIntent; 300 } 301 302 /** 303 * Create the component name for this class 304 * 305 * @param context The Context in which the widgets for this component are created 306 * @return the ComponentName unique to DigitalAppWidgetProvider 307 */ getComponentName(Context context)308 private ComponentName getComponentName(Context context) { 309 if (mComponentName == null) { 310 mComponentName = new ComponentName(context, getClass()); 311 } 312 return mComponentName; 313 } 314 } 315