• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 package com.android.calendar.widget
17 
18 import android.provider.CalendarContract.EXTRA_EVENT_ALL_DAY
19 import android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME
20 import android.provider.CalendarContract.EXTRA_EVENT_END_TIME
21 import android.app.AlarmManager
22 import android.app.PendingIntent
23 import android.appwidget.AppWidgetManager
24 import android.appwidget.AppWidgetProvider
25 import android.content.ComponentName
26 import android.content.Context
27 import android.content.Intent
28 import android.net.Uri
29 import android.provider.CalendarContract
30 import android.text.format.DateUtils
31 import android.text.format.Time
32 import android.util.Log
33 import android.widget.RemoteViews
34 import com.android.calendar.AllInOneActivity
35 import com.android.calendar.EventInfoActivity
36 import com.android.calendar.R
37 import com.android.calendar.Utils
38 
39 /**
40  * Simple widget to show next upcoming calendar event.
41  */
42 class CalendarAppWidgetProvider : AppWidgetProvider() {
43     /**
44      * {@inheritDoc}
45      */
46     @Override
onReceivenull47     override fun onReceive(context: Context?, intent: Intent?) {
48         // Handle calendar-specific updates ourselves because they might be
49         // coming in without extras, which AppWidgetProvider then blocks.
50         val action: String? = intent?.getAction()
51         if (LOGD) Log.d(TAG, "AppWidgetProvider got the intent: " + intent.toString())
52         if (Utils.getWidgetUpdateAction(context as Context).equals(action)) {
53             val appWidgetManager: AppWidgetManager = AppWidgetManager.getInstance(context)
54             performUpdate(
55                 context as Context, appWidgetManager,
56                 appWidgetManager.getAppWidgetIds(getComponentName(context)),
57                 null /* no eventIds */
58             )
59         } else if (action != null && (action.equals(Intent.ACTION_PROVIDER_CHANGED) ||
60             action.equals(Intent.ACTION_TIME_CHANGED) ||
61             action.equals(Intent.ACTION_TIMEZONE_CHANGED) ||
62             action.equals(Intent.ACTION_DATE_CHANGED) ||
63             action.equals(Utils.getWidgetScheduledUpdateAction(context as Context)))
64         ) {
65             val service = Intent(context, CalendarAppWidgetService::class.java)
66             context.startService(service)
67         } else {
68             super.onReceive(context, intent)
69         }
70     }
71 
72     /**
73      * {@inheritDoc}
74      */
75     @Override
onDisablednull76     override fun onDisabled(context: Context) {
77         // Unsubscribe from all AlarmManager updates
78         val am: AlarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
79         val pendingUpdate: PendingIntent = getUpdateIntent(context)
80         am.cancel(pendingUpdate)
81     }
82 
83     /**
84      * {@inheritDoc}
85      */
86     @Override
onUpdatenull87     override fun onUpdate(
88         context: Context,
89         appWidgetManager: AppWidgetManager,
90         appWidgetIds: IntArray
91     ) {
92         performUpdate(context, appWidgetManager,
93             appWidgetIds, null /* no eventIds */)
94     }
95 
96     /**
97      * Process and push out an update for the given appWidgetIds. This call
98      * actually fires an intent to start [CalendarAppWidgetService] as a
99      * background service which handles the actual update, to prevent ANR'ing
100      * during database queries.
101      *
102      * @param context Context to use when starting [CalendarAppWidgetService].
103      * @param appWidgetIds List of specific appWidgetIds to update, or null for
104      * all.
105      * @param changedEventIds Specific events known to be changed. If present,
106      * we use it to decide if an update is necessary.
107      */
performUpdatenull108     private fun performUpdate(
109         context: Context,
110         appWidgetManager: AppWidgetManager,
111         appWidgetIds: IntArray,
112         changedEventIds: LongArray?
113     ) {
114         // Launch over to service so it can perform update
115         for (appWidgetId in appWidgetIds) {
116             if (LOGD) Log.d(TAG, "Building widget update...")
117             val updateIntent = Intent(context, CalendarAppWidgetService::class.java)
118             updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
119             if (changedEventIds != null) {
120                 updateIntent.putExtra(EXTRA_EVENT_IDS, changedEventIds)
121             }
122             updateIntent.setData(Uri.parse(updateIntent.toUri(Intent.URI_INTENT_SCHEME)))
123             val views = RemoteViews(context.getPackageName(), R.layout.appwidget)
124             // Calendar header
125             val time = Time(Utils.getTimeZone(context, null))
126             time.setToNow()
127             val millis: Long = time.toMillis(true)
128             val dayOfWeek: String = DateUtils.getDayOfWeekString(
129                 time.weekDay + 1,
130                 DateUtils.LENGTH_MEDIUM
131             )
132             val date: String? = Utils.formatDateRange(
133                 context, millis, millis,
134                 DateUtils.FORMAT_ABBREV_ALL or DateUtils.FORMAT_SHOW_DATE
135                 or DateUtils.FORMAT_NO_YEAR
136             )
137             views.setTextViewText(R.id.day_of_week, dayOfWeek)
138             views.setTextViewText(R.id.date, date)
139             // Attach to list of events
140             views.setRemoteAdapter(appWidgetId, R.id.events_list, updateIntent)
141             appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.events_list)
142 
143             // Launch calendar app when the user taps on the header
144             val launchCalendarIntent = Intent(Intent.ACTION_VIEW)
145             launchCalendarIntent.setClass(context, AllInOneActivity::class.java)
146             launchCalendarIntent
147                 .setData(Uri.parse("content://com.android.calendar/time/$millis"))
148             val launchCalendarPendingIntent: PendingIntent = PendingIntent.getActivity(
149                 context, 0 /* no requestCode */, launchCalendarIntent,
150                 PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
151             )
152             views.setOnClickPendingIntent(R.id.header, launchCalendarPendingIntent)
153 
154             // Each list item will call setOnClickExtra() to let the list know
155             // which item
156             // is selected by a user.
157             val updateEventIntent: PendingIntent = getLaunchPendingIntentTemplate(context)
158             views.setPendingIntentTemplate(R.id.events_list, updateEventIntent)
159             appWidgetManager.updateAppWidget(appWidgetId, views)
160         }
161     }
162 
163     companion object {
164         const val TAG = "CalendarAppWidgetProvider"
165         const val LOGD = false
166 
167         // TODO Move these to Calendar.java
168         const val EXTRA_EVENT_IDS = "com.android.calendar.EXTRA_EVENT_IDS"
169 
170         /**
171          * Build [ComponentName] describing this specific
172          * [AppWidgetProvider]
173          */
getComponentNamenull174         @JvmStatic fun getComponentName(context: Context?): ComponentName {
175             return ComponentName(context as Context, CalendarAppWidgetProvider::class.java)
176         }
177 
178         /**
179          * Build the [PendingIntent] used to trigger an update of all calendar
180          * widgets. Uses [Utils.getWidgetScheduledUpdateAction] to
181          * directly target all widgets instead of using
182          * [AppWidgetManager.EXTRA_APPWIDGET_IDS].
183          *
184          * @param context Context to use when building broadcast.
185          */
getUpdateIntentnull186         @JvmStatic fun getUpdateIntent(context: Context?): PendingIntent {
187             val intent = Intent(Utils.getWidgetScheduledUpdateAction(context as Context))
188             intent.setDataAndType(CalendarContract.CONTENT_URI, Utils.APPWIDGET_DATA_TYPE)
189             return PendingIntent.getBroadcast(
190                 context, 0 /* no requestCode */, intent,
191                 PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
192             )
193         }
194 
195         /**
196          * Build a [PendingIntent] to launch the Calendar app. This should be used
197          * in combination with [RemoteViews.setPendingIntentTemplate].
198          */
getLaunchPendingIntentTemplatenull199         @JvmStatic fun getLaunchPendingIntentTemplate(context: Context?): PendingIntent {
200             val launchIntent = Intent()
201             launchIntent.setAction(Intent.ACTION_VIEW)
202             launchIntent.setFlags(
203                 Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK or
204                 Intent.FLAG_ACTIVITY_TASK_ON_HOME
205             )
206             launchIntent.setClass(context as Context, AllInOneActivity::class.java)
207             return PendingIntent.getActivity(
208                 context, 0 /* no requestCode */, launchIntent,
209                 PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
210             )
211         }
212 
213         /**
214          * Build an [Intent] available as FillInIntent to launch the Calendar app.
215          * This should be used in combination with
216          * [RemoteViews.setOnClickFillInIntent].
217          * If the go to time is 0, then calendar will be launched without a starting time.
218          *
219          * @param goToTime time that calendar should take the user to, or 0 to
220          * indicate no specific start time.
221          */
getLaunchFillInIntentnull222         @JvmStatic fun getLaunchFillInIntent(
223             context: Context?,
224             id: Long,
225             start: Long,
226             end: Long,
227             allDay: Boolean
228         ): Intent {
229             val fillInIntent = Intent()
230             var dataString = "content://com.android.calendar/events"
231             if (id != 0L) {
232                 fillInIntent.putExtra(Utils.INTENT_KEY_DETAIL_VIEW, true)
233                 fillInIntent.setFlags(
234                     Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK or
235                     Intent.FLAG_ACTIVITY_TASK_ON_HOME
236                 )
237                 dataString += "/$id"
238                 // If we have an event id - start the event info activity
239                 fillInIntent.setClass(context as Context, EventInfoActivity::class.java)
240             } else {
241                 // If we do not have an event id - start AllInOne
242                 fillInIntent.setClass(context as Context, AllInOneActivity::class.java)
243             }
244             val data: Uri = Uri.parse(dataString)
245             fillInIntent.setData(data)
246             fillInIntent.putExtra(EXTRA_EVENT_BEGIN_TIME, start)
247             fillInIntent.putExtra(EXTRA_EVENT_END_TIME, end)
248             fillInIntent.putExtra(EXTRA_EVENT_ALL_DAY, allDay)
249             return fillInIntent
250         }
251     }
252 }
253