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