1 /*
2  * Copyright (C) 2020 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 
17 package com.android.deskclock
18 
19 import android.app.AlarmManager
20 import android.content.BroadcastReceiver
21 import android.content.Context
22 import android.content.Intent
23 import android.content.IntentFilter
24 import android.database.ContentObserver
25 import android.os.BatteryManager
26 import android.os.Bundle
27 import android.os.Handler
28 import android.os.Looper
29 import android.provider.Settings
30 import android.view.View
31 import android.view.View.OnSystemUiVisibilityChangeListener
32 import android.view.ViewTreeObserver.OnPreDrawListener
33 import android.view.Window
34 import android.view.WindowManager
35 import android.widget.TextClock
36 
37 import com.android.deskclock.events.Events
38 import com.android.deskclock.uidata.UiDataModel
39 
40 class ScreensaverActivity : BaseActivity() {
41     private val mStartPositionUpdater: OnPreDrawListener = StartPositionUpdater()
42 
43     private val mIntentReceiver: BroadcastReceiver = object : BroadcastReceiver() {
onReceivenull44         override fun onReceive(context: Context, intent: Intent) {
45             LOGGER.v("ScreensaverActivity onReceive, action: " + intent.action)
46 
47             when (intent.action) {
48                 Intent.ACTION_POWER_CONNECTED -> updateWakeLock(true)
49                 Intent.ACTION_POWER_DISCONNECTED -> updateWakeLock(false)
50                 Intent.ACTION_USER_PRESENT -> finish()
51                 AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED -> {
52                     Utils.refreshAlarm(this@ScreensaverActivity, mContentView)
53                 }
54             }
55         }
56     }
57 
58     /* Register ContentObserver to see alarm changes for pre-L */
59     private val mSettingsContentObserver: ContentObserver? = if (Utils.isPreL) {
60         object : ContentObserver(Handler(Looper.myLooper()!!)) {
onChangenull61             override fun onChange(selfChange: Boolean) {
62                 Utils.refreshAlarm(this@ScreensaverActivity, mContentView)
63             }
64         }
65     } else {
66         null
67     }
68 
69     // Runs every midnight or when the time changes and refreshes the date.
<lambda>null70     private val mMidnightUpdater = Runnable {
71         Utils.updateDate(mDateFormat, mDateFormatForAccessibility, mContentView)
72     }
73 
74     private lateinit var mDateFormat: String
75     private lateinit var mDateFormatForAccessibility: String
76 
77     private lateinit var mContentView: View
78     private lateinit var mMainClockView: View
79 
80     private lateinit var mPositionUpdater: MoveScreensaverRunnable
81 
onCreatenull82     override fun onCreate(savedInstanceState: Bundle?) {
83         super.onCreate(savedInstanceState)
84 
85         mDateFormat = getString(R.string.abbrev_wday_month_day_no_year)
86         mDateFormatForAccessibility = getString(R.string.full_wday_month_day_no_year)
87 
88         setContentView(R.layout.desk_clock_saver)
89         mContentView = findViewById(R.id.saver_container)
90         mMainClockView = mContentView.findViewById(R.id.main_clock)
91 
92         val digitalClock = mMainClockView.findViewById<View>(R.id.digital_clock)
93         val analogClock = mMainClockView.findViewById<View>(R.id.analog_clock) as AnalogClock
94 
95         Utils.setClockIconTypeface(mMainClockView)
96         Utils.setTimeFormat(digitalClock as TextClock, false)
97         Utils.setClockStyle(digitalClock, analogClock)
98         Utils.dimClockView(true, mMainClockView)
99         analogClock.enableSeconds(false)
100 
101         mContentView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LOW_PROFILE
102                 or View.SYSTEM_UI_FLAG_IMMERSIVE
103                 or View.SYSTEM_UI_FLAG_FULLSCREEN
104                 or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
105                 or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
106         mContentView.setOnSystemUiVisibilityChangeListener(InteractionListener())
107 
108         mPositionUpdater = MoveScreensaverRunnable(mContentView, mMainClockView)
109 
110         getIntent()?.let {
111             val eventLabel = it.getIntExtra(Events.EXTRA_EVENT_LABEL, 0)
112             Events.sendScreensaverEvent(R.string.action_show, eventLabel)
113         }
114     }
115 
onStartnull116     override fun onStart() {
117         super.onStart()
118 
119         val filter = IntentFilter()
120         filter.addAction(Intent.ACTION_POWER_CONNECTED)
121         filter.addAction(Intent.ACTION_POWER_DISCONNECTED)
122         filter.addAction(Intent.ACTION_USER_PRESENT)
123         if (Utils.isLOrLater) {
124             filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)
125         }
126         registerReceiver(mIntentReceiver, filter)
127 
128         mSettingsContentObserver?.let {
129             val uri = Settings.System.getUriFor(Settings.System.NEXT_ALARM_FORMATTED)
130             getContentResolver().registerContentObserver(uri, false, it)
131         }
132     }
133 
onResumenull134     override fun onResume() {
135         super.onResume()
136 
137         Utils.updateDate(mDateFormat, mDateFormatForAccessibility, mContentView)
138         Utils.refreshAlarm(this, mContentView)
139 
140         startPositionUpdater()
141         UiDataModel.uiDataModel.addMidnightCallback(mMidnightUpdater)
142 
143         val intent: Intent? = registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
144         val pluggedIn = intent != null && intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0
145         updateWakeLock(pluggedIn)
146     }
147 
onPausenull148     override fun onPause() {
149         super.onPause()
150         UiDataModel.uiDataModel.removePeriodicCallback(mMidnightUpdater)
151         stopPositionUpdater()
152     }
153 
onStopnull154     override fun onStop() {
155         mSettingsContentObserver?.let {
156             getContentResolver().unregisterContentObserver(it)
157         }
158         unregisterReceiver(mIntentReceiver)
159         super.onStop()
160     }
161 
onUserInteractionnull162     override fun onUserInteraction() {
163         // We want the screen saver to exit upon user interaction.
164         finish()
165     }
166 
167     /**
168      * @param pluggedIn `true` iff the device is currently plugged in to a charger
169      */
updateWakeLocknull170     private fun updateWakeLock(pluggedIn: Boolean) {
171         val win: Window = getWindow()
172         val winParams = win.attributes
173         winParams.flags = winParams.flags or WindowManager.LayoutParams.FLAG_FULLSCREEN
174         if (pluggedIn) {
175             winParams.flags = winParams.flags or WINDOW_FLAGS
176         } else {
177             winParams.flags = winParams.flags and WINDOW_FLAGS.inv()
178         }
179         win.attributes = winParams
180     }
181 
182     /**
183      * The [.mContentView] will be drawn shortly. When that draw occurs, the position updater
184      * callback will also be executed to choose a random position for the time display as well as
185      * schedule future callbacks to move the time display each minute.
186      */
startPositionUpdaternull187     private fun startPositionUpdater() {
188         mContentView.viewTreeObserver.addOnPreDrawListener(mStartPositionUpdater)
189     }
190 
191     /**
192      * This activity is no longer in the foreground; position callbacks should be removed.
193      */
stopPositionUpdaternull194     private fun stopPositionUpdater() {
195         mContentView.viewTreeObserver.removeOnPreDrawListener(mStartPositionUpdater)
196         mPositionUpdater.stop()
197     }
198 
199     private inner class StartPositionUpdater : OnPreDrawListener {
200         /**
201          * This callback occurs after initial layout has completed. It is an appropriate place to
202          * select a random position for [.mMainClockView] and schedule future callbacks to update
203          * its position.
204          *
205          * @return `true` to continue with the drawing pass
206          */
onPreDrawnull207         override fun onPreDraw(): Boolean {
208             if (mContentView.viewTreeObserver.isAlive) {
209                 // Start the periodic position updater.
210                 mPositionUpdater.start()
211 
212                 // This listener must now be removed to avoid starting the position updater again.
213                 mContentView.viewTreeObserver.removeOnPreDrawListener(mStartPositionUpdater)
214             }
215             return true
216         }
217     }
218 
219     private inner class InteractionListener : OnSystemUiVisibilityChangeListener {
onSystemUiVisibilityChangenull220         override fun onSystemUiVisibilityChange(visibility: Int) {
221             // When the user interacts with the screen, the navigation bar reappears
222             if (visibility and View.SYSTEM_UI_FLAG_HIDE_NAVIGATION == 0) {
223                 // We want the screen saver to exit upon user interaction.
224                 finish()
225             }
226         }
227     }
228 
229     companion object {
230         private val LOGGER = LogUtils.Logger("ScreensaverActivity")
231 
232         /** These flags keep the screen on if the device is plugged in.  */
233         private const val WINDOW_FLAGS = (WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
234                 or WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
235                 or WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
236                 or WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
237     }
238 }