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 17package com.android.alarmclock; 18 19import android.app.AlarmManager; 20import android.app.PendingIntent; 21import android.appwidget.AppWidgetManager; 22import android.appwidget.AppWidgetProvider; 23import android.appwidget.AppWidgetProviderInfo; 24import android.content.ComponentName; 25import android.content.Context; 26import android.content.Intent; 27import android.net.Uri; 28import android.os.Bundle; 29import android.provider.Settings; 30import android.text.TextUtils; 31import android.text.format.DateFormat; 32import android.util.Log; 33import android.view.View; 34import android.widget.RemoteViews; 35 36import com.android.deskclock.DeskClock; 37import com.android.deskclock.R; 38import com.android.deskclock.Utils; 39import com.android.deskclock.alarms.AlarmNotifications; 40import com.android.deskclock.worldclock.Cities; 41import com.android.deskclock.worldclock.CitiesActivity; 42 43import java.util.Locale; 44 45public class DigitalAppWidgetProvider extends AppWidgetProvider { 46 private static final String TAG = "DigitalAppWidgetProvider"; 47 48 /** 49 * Intent to be used for checking if a world clock's date has changed. Must be every fifteen 50 * minutes because not all time zones are hour-locked. 51 **/ 52 public static final String ACTION_ON_QUARTER_HOUR = "com.android.deskclock.ON_QUARTER_HOUR"; 53 54 // Lazily creating this intent to use with the AlarmManager 55 private PendingIntent mPendingIntent; 56 // Lazily creating this name to use with the AppWidgetManager 57 private ComponentName mComponentName; 58 59 public DigitalAppWidgetProvider() { 60 } 61 62 @Override 63 public void onEnabled(Context context) { 64 super.onEnabled(context); 65 startAlarmOnQuarterHour(context); 66 } 67 68 @Override 69 public void onDisabled(Context context) { 70 super.onDisabled(context); 71 cancelAlarmOnQuarterHour(context); 72 } 73 74 @Override 75 public void onReceive(Context context, Intent intent) { 76 String action = intent.getAction(); 77 if (DigitalAppWidgetService.LOGGING) { 78 Log.i(TAG, "onReceive: " + action); 79 } 80 super.onReceive(context, intent); 81 82 if (ACTION_ON_QUARTER_HOUR.equals(action) 83 || Intent.ACTION_DATE_CHANGED.equals(action) 84 || Intent.ACTION_TIMEZONE_CHANGED.equals(action) 85 || Intent.ACTION_TIME_CHANGED.equals(action) 86 || Intent.ACTION_LOCALE_CHANGED.equals(action)) { 87 AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); 88 if (appWidgetManager != null) { 89 int[] appWidgetIds = appWidgetManager.getAppWidgetIds(getComponentName(context)); 90 for (int appWidgetId : appWidgetIds) { 91 appWidgetManager. 92 notifyAppWidgetViewDataChanged(appWidgetId, 93 R.id.digital_appwidget_listview); 94 RemoteViews widget = new RemoteViews(context.getPackageName(), 95 R.layout.digital_appwidget); 96 float ratio = WidgetUtils.getScaleRatio(context, null, appWidgetId); 97 WidgetUtils.setTimeFormat(widget, 0/*no am/pm*/, R.id.the_clock); 98 WidgetUtils.setClockSize(context, widget, ratio); 99 refreshAlarm(context, widget); 100 appWidgetManager.partiallyUpdateAppWidget(appWidgetId, widget); 101 } 102 } 103 if(!ACTION_ON_QUARTER_HOUR.equals(action)) { 104 cancelAlarmOnQuarterHour(context); 105 } 106 startAlarmOnQuarterHour(context); 107 } else if (AlarmNotifications.SYSTEM_ALARM_CHANGE_ACTION.equals(action) 108 || Intent.ACTION_SCREEN_ON.equals(action)) { 109 // Refresh the next alarm 110 AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); 111 if (appWidgetManager != null) { 112 int[] appWidgetIds = appWidgetManager.getAppWidgetIds(getComponentName(context)); 113 for (int appWidgetId : appWidgetIds) { 114 RemoteViews widget = new RemoteViews(context.getPackageName(), 115 R.layout.digital_appwidget); 116 refreshAlarm(context, widget); 117 appWidgetManager.partiallyUpdateAppWidget(appWidgetId, widget); 118 } 119 } 120 } else if (Cities.WORLDCLOCK_UPDATE_INTENT.equals(action)) { 121 // Refresh the world cities list 122 AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); 123 if (appWidgetManager != null) { 124 int[] appWidgetIds = appWidgetManager.getAppWidgetIds(getComponentName(context)); 125 for (int appWidgetId : appWidgetIds) { 126 appWidgetManager. 127 notifyAppWidgetViewDataChanged(appWidgetId, 128 R.id.digital_appwidget_listview); 129 } 130 } 131 } 132 } 133 134 @Override 135 public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { 136 if (DigitalAppWidgetService.LOGGING) { 137 Log.i(TAG, "onUpdate"); 138 } 139 for (int appWidgetId : appWidgetIds) { 140 float ratio = WidgetUtils.getScaleRatio(context, null, appWidgetId); 141 updateClock(context, appWidgetManager, appWidgetId, ratio); 142 } 143 startAlarmOnQuarterHour(context); 144 super.onUpdate(context, appWidgetManager, appWidgetIds); 145 } 146 147 @Override 148 public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, 149 int appWidgetId, Bundle newOptions) { 150 // scale the fonts of the clock to fit inside the new size 151 float ratio = WidgetUtils.getScaleRatio(context, newOptions, appWidgetId); 152 AppWidgetManager widgetManager = AppWidgetManager.getInstance(context); 153 updateClock(context, widgetManager, appWidgetId, ratio); 154 } 155 156 private void updateClock( 157 Context context, AppWidgetManager appWidgetManager, int appWidgetId, float ratio) { 158 RemoteViews widget = new RemoteViews(context.getPackageName(), R.layout.digital_appwidget); 159 160 // Launch clock when clicking on the time in the widget only if not a lock screen widget 161 Bundle newOptions = appWidgetManager.getAppWidgetOptions(appWidgetId); 162 if (newOptions != null && 163 newOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY, -1) 164 != AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD) { 165 widget.setOnClickPendingIntent(R.id.digital_appwidget, 166 PendingIntent.getActivity(context, 0, new Intent(context, DeskClock.class), 0)); 167 } 168 169 // Setup alarm text clock's format and font sizes 170 refreshAlarm(context, widget); 171 WidgetUtils.setTimeFormat(widget, 0/*no am/pm*/, R.id.the_clock); 172 WidgetUtils.setClockSize(context, widget, ratio); 173 174 // Set today's date format 175 CharSequence dateFormat = DateFormat.getBestDateTimePattern(Locale.getDefault(), 176 context.getString(R.string.abbrev_wday_month_day_no_year)); 177 widget.setCharSequence(R.id.date, "setFormat12Hour", dateFormat); 178 widget.setCharSequence(R.id.date, "setFormat24Hour", dateFormat); 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 widget.setPendingIntentTemplate(R.id.digital_appwidget_listview, 190 PendingIntent. 191 getActivity(context, 0, new Intent(context, CitiesActivity.class), 0)); 192 193 // Refresh the widget 194 appWidgetManager.notifyAppWidgetViewDataChanged( 195 appWidgetId, R.id.digital_appwidget_listview); 196 appWidgetManager.updateAppWidget(appWidgetId, widget); 197 } 198 199 protected void refreshAlarm(Context context, RemoteViews widget) { 200 String nextAlarm = Settings.System.getString(context.getContentResolver(), 201 Settings.System.NEXT_ALARM_FORMATTED); 202 if (!TextUtils.isEmpty(nextAlarm)) { 203 widget.setTextViewText(R.id.nextAlarm, 204 context.getString(R.string.control_set_alarm_with_existing, nextAlarm)); 205 widget.setViewVisibility(R.id.nextAlarm, View.VISIBLE); 206 if (DigitalAppWidgetService.LOGGING) { 207 Log.v(TAG, "DigitalWidget sets next alarm string to " + nextAlarm); 208 } 209 } else { 210 widget.setViewVisibility(R.id.nextAlarm, View.GONE); 211 if (DigitalAppWidgetService.LOGGING) { 212 Log.v(TAG, "DigitalWidget sets next alarm string to null"); 213 } 214 } 215 } 216 217 /** 218 * Start an alarm that fires on the next quarter hour to update the world clock city 219 * day when the local time or the world city crosses midnight. 220 * 221 * @param context The context in which the PendingIntent should perform the broadcast. 222 */ 223 private void startAlarmOnQuarterHour(Context context) { 224 if (context != null) { 225 long onQuarterHour = Utils.getAlarmOnQuarterHour(); 226 PendingIntent quarterlyIntent = getOnQuarterHourPendingIntent(context); 227 AlarmManager alarmManager = ((AlarmManager) context 228 .getSystemService(Context.ALARM_SERVICE)); 229 if (Utils.isKitKatOrLater()) { 230 alarmManager.setExact(AlarmManager.RTC, onQuarterHour, quarterlyIntent); 231 } else { 232 alarmManager.set(AlarmManager.RTC, onQuarterHour, quarterlyIntent); 233 } 234 if (DigitalAppWidgetService.LOGGING) { 235 Log.v(TAG, "startAlarmOnQuarterHour " + context.toString()); 236 } 237 } 238 } 239 240 241 /** 242 * Remove the alarm for the quarter hour update. 243 * 244 * @param context The context in which the PendingIntent was started to perform the broadcast. 245 */ 246 public void cancelAlarmOnQuarterHour(Context context) { 247 if (context != null) { 248 PendingIntent quarterlyIntent = getOnQuarterHourPendingIntent(context); 249 if (DigitalAppWidgetService.LOGGING) { 250 Log.v(TAG, "cancelAlarmOnQuarterHour " + context.toString()); 251 } 252 ((AlarmManager) context.getSystemService(Context.ALARM_SERVICE)).cancel( 253 quarterlyIntent); 254 } 255 } 256 257 /** 258 * Create the pending intent that is broadcast on the quarter hour. 259 * 260 * @param context The Context in which this PendingIntent should perform the broadcast. 261 * @return a pending intent with an intent unique to DigitalAppWidgetProvider 262 */ 263 private PendingIntent getOnQuarterHourPendingIntent(Context context) { 264 if (mPendingIntent == null) { 265 mPendingIntent = PendingIntent.getBroadcast(context, 0, 266 new Intent(ACTION_ON_QUARTER_HOUR), PendingIntent.FLAG_CANCEL_CURRENT); 267 } 268 return mPendingIntent; 269 } 270 271 /** 272 * Create the component name for this class 273 * 274 * @param context The Context in which the widgets for this component are created 275 * @return the ComponentName unique to DigitalAppWidgetProvider 276 */ 277 private ComponentName getComponentName(Context context) { 278 if (mComponentName == null) { 279 mComponentName = new ComponentName(context, getClass()); 280 } 281 return mComponentName; 282 } 283} 284