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