• 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, 0 /* no flags */
150             )
151             views.setOnClickPendingIntent(R.id.header, launchCalendarPendingIntent)
152 
153             // Each list item will call setOnClickExtra() to let the list know
154             // which item
155             // is selected by a user.
156             val updateEventIntent: PendingIntent = getLaunchPendingIntentTemplate(context)
157             views.setPendingIntentTemplate(R.id.events_list, updateEventIntent)
158             appWidgetManager.updateAppWidget(appWidgetId, views)
159         }
160     }
161 
162     companion object {
163         const val TAG = "CalendarAppWidgetProvider"
164         const val LOGD = false
165 
166         // TODO Move these to Calendar.java
167         const val EXTRA_EVENT_IDS = "com.android.calendar.EXTRA_EVENT_IDS"
168 
169         /**
170          * Build [ComponentName] describing this specific
171          * [AppWidgetProvider]
172          */
getComponentNamenull173         @JvmStatic fun getComponentName(context: Context?): ComponentName {
174             return ComponentName(context as Context, CalendarAppWidgetProvider::class.java)
175         }
176 
177         /**
178          * Build the [PendingIntent] used to trigger an update of all calendar
179          * widgets. Uses [Utils.getWidgetScheduledUpdateAction] to
180          * directly target all widgets instead of using
181          * [AppWidgetManager.EXTRA_APPWIDGET_IDS].
182          *
183          * @param context Context to use when building broadcast.
184          */
getUpdateIntentnull185         @JvmStatic fun getUpdateIntent(context: Context?): PendingIntent {
186             val intent = Intent(Utils.getWidgetScheduledUpdateAction(context as Context))
187             intent.setDataAndType(CalendarContract.CONTENT_URI, Utils.APPWIDGET_DATA_TYPE)
188             return PendingIntent.getBroadcast(
189                 context, 0 /* no requestCode */, intent,
190                 0 /* no flags */
191             )
192         }
193 
194         /**
195          * Build a [PendingIntent] to launch the Calendar app. This should be used
196          * in combination with [RemoteViews.setPendingIntentTemplate].
197          */
getLaunchPendingIntentTemplatenull198         @JvmStatic fun getLaunchPendingIntentTemplate(context: Context?): PendingIntent {
199             val launchIntent = Intent()
200             launchIntent.setAction(Intent.ACTION_VIEW)
201             launchIntent.setFlags(
202                 Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK or
203                 Intent.FLAG_ACTIVITY_TASK_ON_HOME
204             )
205             launchIntent.setClass(context as Context, AllInOneActivity::class.java)
206             return PendingIntent.getActivity(
207                 context, 0 /* no requestCode */, launchIntent,
208                 PendingIntent.FLAG_UPDATE_CURRENT
209             )
210         }
211 
212         /**
213          * Build an [Intent] available as FillInIntent to launch the Calendar app.
214          * This should be used in combination with
215          * [RemoteViews.setOnClickFillInIntent].
216          * If the go to time is 0, then calendar will be launched without a starting time.
217          *
218          * @param goToTime time that calendar should take the user to, or 0 to
219          * indicate no specific start time.
220          */
getLaunchFillInIntentnull221         @JvmStatic fun getLaunchFillInIntent(
222             context: Context?,
223             id: Long,
224             start: Long,
225             end: Long,
226             allDay: Boolean
227         ): Intent {
228             val fillInIntent = Intent()
229             var dataString = "content://com.android.calendar/events"
230             if (id != 0L) {
231                 fillInIntent.putExtra(Utils.INTENT_KEY_DETAIL_VIEW, true)
232                 fillInIntent.setFlags(
233                     Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK or
234                     Intent.FLAG_ACTIVITY_TASK_ON_HOME
235                 )
236                 dataString += "/$id"
237                 // If we have an event id - start the event info activity
238                 fillInIntent.setClass(context as Context, EventInfoActivity::class.java)
239             } else {
240                 // If we do not have an event id - start AllInOne
241                 fillInIntent.setClass(context as Context, AllInOneActivity::class.java)
242             }
243             val data: Uri = Uri.parse(dataString)
244             fillInIntent.setData(data)
245             fillInIntent.putExtra(EXTRA_EVENT_BEGIN_TIME, start)
246             fillInIntent.putExtra(EXTRA_EVENT_END_TIME, end)
247             fillInIntent.putExtra(EXTRA_EVENT_ALL_DAY, allDay)
248             return fillInIntent
249         }
250     }
251 }