Home | History | Annotate | Download | only in alarmclock
      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 
     60     public DigitalAppWidgetProvider() {
     61     }
     62 
     63     @Override
     64     public void onEnabled(Context context) {
     65         super.onEnabled(context);
     66         startAlarmOnQuarterHour(context);
     67     }
     68 
     69     @Override
     70     public void onDisabled(Context context) {
     71         super.onDisabled(context);
     72         cancelAlarmOnQuarterHour(context);
     73     }
     74 
     75     @Override
     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
    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
    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      */
    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 
    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 
    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 
    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      */
    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      */
    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      */
    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      */
    308     private ComponentName getComponentName(Context context) {
    309         if (mComponentName == null) {
    310             mComponentName = new ComponentName(context, getClass());
    311         }
    312         return mComponentName;
    313     }
    314 }
    315