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.alerts
17 
18 import android.app.IntentService
19 import android.app.NotificationManager
20 import android.content.ContentResolver
21 import android.content.ContentValues
22 import android.content.Context
23 import android.content.Intent
24 import android.net.Uri
25 import android.os.IBinder
26 import android.provider.CalendarContract.CalendarAlerts
27 import androidx.core.app.TaskStackBuilder
28 import android.util.Log
29 import com.android.calendar.EventInfoActivity
30 import com.android.calendar.alerts.GlobalDismissManager.AlarmId
31 import java.util.LinkedList
32 import java.util.List
33 
34 /**
35  * Service for asynchronously marking fired alarms as dismissed.
36  */
37 class DismissAlarmsService : IntentService("DismissAlarmsService") {
38     @Override
onBindnull39     override fun onBind(intent: Intent?): IBinder? {
40         return null
41     }
42 
43     @Override
onHandleIntentnull44     override fun onHandleIntent(intent: Intent?) {
45         if (AlertService.DEBUG) {
46             Log.d(TAG, "onReceive: a=" + intent?.getAction().toString() + " " + intent.toString())
47         }
48         val eventId = intent?.getLongExtra(AlertUtils.EVENT_ID_KEY, -1)
49         val eventStart = intent?.getLongExtra(AlertUtils.EVENT_START_KEY, -1)
50         val eventEnd = intent?.getLongExtra(AlertUtils.EVENT_END_KEY, -1)
51         val eventIds = intent?.getLongArrayExtra(AlertUtils.EVENT_IDS_KEY)
52         val eventStarts = intent?.getLongArrayExtra(AlertUtils.EVENT_STARTS_KEY)
53         val notificationId = intent?.getIntExtra(AlertUtils.NOTIFICATION_ID_KEY, -1)
54         val alarmIds = LinkedList<AlarmId>()
55         val uri: Uri = CalendarAlerts.CONTENT_URI
56         val selection: String
57 
58         // Dismiss a specific fired alarm if id is present, otherwise, dismiss all alarms
59         if (eventId != -1L) {
60             alarmIds.add(AlarmId(eventId as Long, eventStart as Long))
61             selection =
62                 CalendarAlerts.STATE.toString() + "=" + CalendarAlerts.STATE_FIRED + " AND " +
63                     CalendarAlerts.EVENT_ID + "=" + eventId
64         } else if (eventIds != null && eventIds.size > 0 && eventStarts != null &&
65             eventIds.size == eventStarts.size) {
66             selection = buildMultipleEventsQuery(eventIds)
67             for (i in eventIds.indices) {
68                 alarmIds.add(AlarmId(eventIds[i], eventStarts[i]))
69             }
70         } else {
71             // NOTE: I don't believe that this ever happens.
72             selection = CalendarAlerts.STATE.toString() + "=" + CalendarAlerts.STATE_FIRED
73         }
74         GlobalDismissManager.dismissGlobally(getApplicationContext(),
75             alarmIds as List<GlobalDismissManager.AlarmId>)
76         val resolver: ContentResolver = getContentResolver()
77         val values = ContentValues()
78         values.put(PROJECTION[COLUMN_INDEX_STATE], CalendarAlerts.STATE_DISMISSED)
79         resolver.update(uri, values, selection, null)
80 
81         // Remove from notification bar.
82         if (notificationId != -1) {
83             val nm: NotificationManager =
84                 getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
85             nm.cancel(notificationId as Int)
86         }
87         if (SHOW_ACTION.equals(intent.getAction())) {
88             // Show event on Calendar app by building an intent and task stack to start
89             // EventInfoActivity with AllInOneActivity as the parent activity rooted to home.
90             val i: Intent = AlertUtils.buildEventViewIntent(this, eventId as Long,
91                                                             eventStart as Long, eventEnd as Long)
92             TaskStackBuilder.create(this)
93                 .addParentStack(EventInfoActivity::class.java).addNextIntent(i).startActivities()
94         }
95     }
96 
buildMultipleEventsQuerynull97     private fun buildMultipleEventsQuery(eventIds: LongArray): String {
98         val selection = StringBuilder()
99         selection.append(CalendarAlerts.STATE)
100         selection.append("=")
101         selection.append(CalendarAlerts.STATE_FIRED)
102         if (eventIds.size > 0) {
103             selection.append(" AND (")
104             selection.append(CalendarAlerts.EVENT_ID)
105             selection.append("=")
106             selection.append(eventIds[0])
107             for (i in 1 until eventIds.size) {
108                 selection.append(" OR ")
109                 selection.append(CalendarAlerts.EVENT_ID)
110                 selection.append("=")
111                 selection.append(eventIds[i])
112             }
113             selection.append(")")
114         }
115         return selection.toString()
116     }
117 
118     companion object {
119         private const val TAG = "DismissAlarmsService"
120         const val SHOW_ACTION = "com.android.calendar.SHOW"
121         const val DISMISS_ACTION = "com.android.calendar.DISMISS"
122         private val PROJECTION = arrayOf<String>(
123             CalendarAlerts.STATE
124         )
125         private const val COLUMN_INDEX_STATE = 0
126     }
127 }