• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License
10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11  * or implied. See the License for the specific language governing permissions and limitations under
12  * the License.
13  */
14 
15 package com.android.deskclock.timer
16 
17 import android.content.Intent
18 import android.content.pm.ActivityInfo
19 import android.os.Bundle
20 import android.os.SystemClock
21 import android.text.TextUtils
22 import android.transition.AutoTransition
23 import android.transition.TransitionManager
24 import android.widget.FrameLayout
25 import android.widget.TextView
26 import android.view.Gravity
27 import android.view.KeyEvent
28 import android.view.View
29 import android.view.ViewGroup
30 import android.view.WindowManager
31 
32 import com.android.deskclock.BaseActivity
33 import com.android.deskclock.LogUtils
34 import com.android.deskclock.R
35 import com.android.deskclock.data.DataModel
36 import com.android.deskclock.data.Timer
37 import com.android.deskclock.data.TimerListener
38 
39 /**
40  * This activity is designed to be shown over the lock screen. As such, it displays the expired
41  * timers and a single button to reset them all. Each expired timer can also be reset to one minute
42  * with a button in the user interface. All other timer operations are disabled in this activity.
43  */
44 class ExpiredTimersActivity : BaseActivity() {
45     /** Scheduled to update the timers while at least one is expired.  */
46     private val mTimeUpdateRunnable: Runnable = TimeUpdateRunnable()
47 
48     /** Updates the timers displayed in this activity as the backing data changes.  */
49     private val mTimerChangeWatcher: TimerListener = TimerChangeWatcher()
50 
51     /** The scene root for transitions when expired timers are added/removed from this container. */
52     private lateinit var mExpiredTimersScrollView: ViewGroup
53 
54     /** Displays the expired timers.  */
55     private lateinit var mExpiredTimersView: ViewGroup
56 
onCreatenull57     override fun onCreate(savedInstanceState: Bundle?) {
58         super.onCreate(savedInstanceState)
59 
60         val expiredTimers = expiredTimers
61 
62         // If no expired timers, finish
63         if (expiredTimers.size == 0) {
64             LogUtils.i("No expired timers, skipping display.")
65             finish()
66             return
67         }
68 
69         setContentView(R.layout.expired_timers_activity)
70 
71         mExpiredTimersView = findViewById(R.id.expired_timers_list) as ViewGroup
72         mExpiredTimersScrollView = findViewById(R.id.expired_timers_scroll) as ViewGroup
73 
74         (findViewById(R.id.fab) as View).setOnClickListener(FabClickListener())
75 
76         val view: View = findViewById(R.id.expired_timers_activity)
77         view.systemUiVisibility = View.SYSTEM_UI_FLAG_LOW_PROFILE
78 
79         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
80                 or WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON)
81 
82         setTurnScreenOn(true)
83         setShowWhenLocked(true)
84 
85         // Close dialogs and window shade, so this is fully visible
86         sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
87 
88         // Honor rotation on tablets; fix the orientation on phones.
89         if (!getResources().getBoolean(R.bool.rotateAlarmAlert)) {
90             setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR)
91         }
92 
93         // Create views for each of the expired timers.
94         for (timer in expiredTimers) {
95             addTimer(timer)
96         }
97 
98         // Update views in response to timer data changes.
99         DataModel.dataModel.addTimerListener(mTimerChangeWatcher)
100     }
101 
onResumenull102     override fun onResume() {
103         super.onResume()
104         startUpdatingTime()
105     }
106 
onPausenull107     override fun onPause() {
108         super.onPause()
109         stopUpdatingTime()
110     }
111 
onDestroynull112     override fun onDestroy() {
113         super.onDestroy()
114         DataModel.dataModel.removeTimerListener(mTimerChangeWatcher)
115     }
116 
dispatchKeyEventnull117     override fun dispatchKeyEvent(event: KeyEvent): Boolean {
118         if (event.action == KeyEvent.ACTION_UP) {
119             when (event.keyCode) {
120                 KeyEvent.KEYCODE_VOLUME_UP,
121                 KeyEvent.KEYCODE_VOLUME_DOWN,
122                 KeyEvent.KEYCODE_VOLUME_MUTE,
123                 KeyEvent.KEYCODE_CAMERA,
124                 KeyEvent.KEYCODE_FOCUS -> {
125                     DataModel.dataModel.resetOrDeleteExpiredTimers(R.string.label_hardware_button)
126                     return true
127                 }
128             }
129         }
130         return super.dispatchKeyEvent(event)
131     }
132 
133     /**
134      * Post the first runnable to update times within the UI. It will reschedule itself as needed.
135      */
startUpdatingTimenull136     private fun startUpdatingTime() {
137         // Ensure only one copy of the runnable is ever scheduled by first stopping updates.
138         stopUpdatingTime()
139         mExpiredTimersView.post(mTimeUpdateRunnable)
140     }
141 
142     /**
143      * Remove the runnable that updates times within the UI.
144      */
stopUpdatingTimenull145     private fun stopUpdatingTime() {
146         mExpiredTimersView.removeCallbacks(mTimeUpdateRunnable)
147     }
148 
149     /**
150      * Create and add a new view that corresponds with the given `timer`.
151      */
addTimernull152     private fun addTimer(timer: Timer) {
153         TransitionManager.beginDelayedTransition(mExpiredTimersScrollView, AutoTransition())
154 
155         val timerId: Int = timer.id
156         val timerItem = getLayoutInflater()
157                 .inflate(R.layout.timer_item, mExpiredTimersView, false) as TimerItem
158         // Store the timer id as a tag on the view so it can be located on delete.
159         timerItem.id = timerId
160         mExpiredTimersView.addView(timerItem)
161 
162         // Hide the label hint for expired timers.
163         val labelView = timerItem.findViewById<View>(R.id.timer_label) as TextView
164         labelView.hint = null
165         labelView.visibility = if (TextUtils.isEmpty(timer.label)) View.GONE else View.VISIBLE
166 
167         // Add logic to the "Add 1 Minute" button.
168         val addMinuteButton = timerItem.findViewById<View>(R.id.reset_add)
169         addMinuteButton.setOnClickListener {
170             val timer: Timer = DataModel.dataModel.getTimer(timerId)!!
171             DataModel.dataModel.addTimerMinute(timer)
172         }
173 
174         // If the first timer was just added, center it.
175         val expiredTimers = expiredTimers
176         if (expiredTimers.size == 1) {
177             centerFirstTimer()
178         } else if (expiredTimers.size == 2) {
179             uncenterFirstTimer()
180         }
181     }
182 
183     /**
184      * Remove an existing view that corresponds with the given `timer`.
185      */
removeTimernull186     private fun removeTimer(timer: Timer) {
187         TransitionManager.beginDelayedTransition(mExpiredTimersScrollView, AutoTransition())
188 
189         val timerId: Int = timer.id
190         val count = mExpiredTimersView.childCount
191         for (i in 0 until count) {
192             val timerView = mExpiredTimersView.getChildAt(i)
193             if (timerView.id == timerId) {
194                 mExpiredTimersView.removeView(timerView)
195                 break
196             }
197         }
198 
199         // If the second last timer was just removed, center the last timer.
200         val expiredTimers = expiredTimers
201         if (expiredTimers.isEmpty()) {
202             finish()
203         } else if (expiredTimers.size == 1) {
204             centerFirstTimer()
205         }
206     }
207 
208     /**
209      * Center the single timer.
210      */
centerFirstTimernull211     private fun centerFirstTimer() {
212         val lp = mExpiredTimersView.layoutParams as FrameLayout.LayoutParams
213         lp.gravity = Gravity.CENTER
214         mExpiredTimersView.requestLayout()
215     }
216 
217     /**
218      * Display the multiple timers as a scrollable list.
219      */
uncenterFirstTimernull220     private fun uncenterFirstTimer() {
221         val lp = mExpiredTimersView.layoutParams as FrameLayout.LayoutParams
222         lp.gravity = Gravity.NO_GRAVITY
223         mExpiredTimersView.requestLayout()
224     }
225 
226     private val expiredTimers: List<Timer>
227         get() = DataModel.dataModel.expiredTimers
228 
229     /**
230      * Periodically refreshes the state of each timer.
231      */
232     private inner class TimeUpdateRunnable : Runnable {
runnull233         override fun run() {
234             val startTime = SystemClock.elapsedRealtime()
235 
236             val count = mExpiredTimersView.childCount
237             for (i in 0 until count) {
238                 val timerItem = mExpiredTimersView.getChildAt(i) as TimerItem
239                 val timer: Timer? = DataModel.dataModel.getTimer(timerItem.id)
240                 if (timer != null) {
241                     timerItem.update(timer)
242                 }
243             }
244 
245             val endTime = SystemClock.elapsedRealtime()
246 
247             // Try to maintain a consistent period of time between redraws.
248             val delay = Math.max(0L, startTime + 20L - endTime)
249             mExpiredTimersView.postDelayed(this, delay)
250         }
251     }
252 
253     /**
254      * Clicking the fab resets all expired timers.
255      */
256     private inner class FabClickListener : View.OnClickListener {
onClicknull257         override fun onClick(v: View) {
258             stopUpdatingTime()
259             DataModel.dataModel.removeTimerListener(mTimerChangeWatcher)
260             DataModel.dataModel.resetOrDeleteExpiredTimers(R.string.label_deskclock)
261             finish()
262         }
263     }
264 
265     /**
266      * Adds and removes expired timers from this activity based on their state changes.
267      */
268     private inner class TimerChangeWatcher : TimerListener {
timerAddednull269         override fun timerAdded(timer: Timer) {
270             if (timer.isExpired) {
271                 addTimer(timer)
272             }
273         }
274 
timerUpdatednull275         override fun timerUpdated(before: Timer, after: Timer) {
276             if (!before.isExpired && after.isExpired) {
277                 addTimer(after)
278             } else if (before.isExpired && !after.isExpired) {
279                 removeTimer(before)
280             }
281         }
282 
timerRemovednull283         override fun timerRemoved(timer: Timer) {
284             if (timer.isExpired) {
285                 removeTimer(timer)
286             }
287         }
288     }
289 }