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.content.res.Configuration
25 import android.database.ContentObserver
26 import android.os.Handler
27 import android.os.Looper
28 import android.provider.Settings
29 import android.service.dreams.DreamService
30 import android.view.View
31 import android.view.ViewTreeObserver.OnPreDrawListener
32 import android.widget.TextClock
33 
34 import com.android.deskclock.data.DataModel
35 import com.android.deskclock.uidata.UiDataModel
36 
37 class Screensaver : DreamService() {
38     private val mStartPositionUpdater: OnPreDrawListener = StartPositionUpdater()
39     private var mPositionUpdater: MoveScreensaverRunnable? = null
40 
41     private var mDateFormat: String? = null
42     private var mDateFormatForAccessibility: String? = null
43 
44     private var mContentView: View? = null
45     private var mMainClockView: View? = null
46     private var mDigitalClock: TextClock? = null
47     private var mAnalogClock: AnalogClock? = null
48 
49     /* Register ContentObserver to see alarm changes for pre-L */
50     private val mSettingsContentObserver: ContentObserver? = if (Utils.isLOrLater) {
51         null
52     } else {
53         object : ContentObserver(Handler(Looper.myLooper()!!)) {
onChangenull54             override fun onChange(selfChange: Boolean) {
55                 Utils.refreshAlarm(this@Screensaver, mContentView)
56             }
57         }
58     }
59 
60     // Runs every midnight or when the time changes and refreshes the date.
<lambda>null61     private val mMidnightUpdater = Runnable {
62         Utils.updateDate(mDateFormat, mDateFormatForAccessibility, mContentView)
63     }
64 
65     /**
66      * Receiver to alarm clock changes.
67      */
68     private val mAlarmChangedReceiver: BroadcastReceiver = object : BroadcastReceiver() {
onReceivenull69         override fun onReceive(context: Context, intent: Intent) {
70             Utils.refreshAlarm(this@Screensaver, mContentView)
71         }
72     }
73 
onCreatenull74     override fun onCreate() {
75         LOGGER.v("Screensaver created")
76 
77         setTheme(R.style.Theme_DeskClock)
78         super.onCreate()
79 
80         mDateFormat = getString(R.string.abbrev_wday_month_day_no_year)
81         mDateFormatForAccessibility = getString(R.string.full_wday_month_day_no_year)
82     }
83 
onAttachedToWindownull84     override fun onAttachedToWindow() {
85         LOGGER.v("Screensaver attached to window")
86         super.onAttachedToWindow()
87 
88         setContentView(R.layout.desk_clock_saver)
89 
90         mContentView = findViewById(R.id.saver_container)
91         mMainClockView = mContentView?.findViewById(R.id.main_clock)
92         mDigitalClock = mMainClockView?.findViewById<View>(R.id.digital_clock) as TextClock
93         mAnalogClock = mMainClockView?.findViewById<View>(R.id.analog_clock) as AnalogClock
94 
95         setClockStyle()
96         Utils.setClockIconTypeface(mContentView)
97         Utils.setTimeFormat(mDigitalClock, false)
98         mAnalogClock?.enableSeconds(false)
99 
100         mContentView?.systemUiVisibility = (View.SYSTEM_UI_FLAG_LOW_PROFILE
101                 or View.SYSTEM_UI_FLAG_IMMERSIVE
102                 or View.SYSTEM_UI_FLAG_FULLSCREEN
103                 or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
104                 or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
105 
106         mPositionUpdater = MoveScreensaverRunnable(mContentView!!, mMainClockView!!)
107 
108         // We want the screen saver to exit upon user interaction.
109         isInteractive = false
110         isFullscreen = true
111 
112         // Setup handlers for time reference changes and date updates.
113         if (Utils.isLOrLater) {
114             registerReceiver(mAlarmChangedReceiver,
115                     IntentFilter(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED))
116         }
117 
118         mSettingsContentObserver?.let {
119             val uri = Settings.System.getUriFor(Settings.System.NEXT_ALARM_FORMATTED)
120             contentResolver.registerContentObserver(uri, false, it)
121         }
122 
123         Utils.updateDate(mDateFormat, mDateFormatForAccessibility, mContentView)
124         Utils.refreshAlarm(this, mContentView)
125 
126         startPositionUpdater()
127         UiDataModel.uiDataModel.addMidnightCallback(mMidnightUpdater)
128     }
129 
onDetachedFromWindownull130     override fun onDetachedFromWindow() {
131         LOGGER.v("Screensaver detached from window")
132         super.onDetachedFromWindow()
133 
134         mSettingsContentObserver?.let {
135             contentResolver.unregisterContentObserver(it)
136         }
137 
138         UiDataModel.uiDataModel.removePeriodicCallback(mMidnightUpdater)
139         stopPositionUpdater()
140 
141         // Tear down handlers for time reference changes and date updates.
142         if (Utils.isLOrLater) {
143             unregisterReceiver(mAlarmChangedReceiver)
144         }
145     }
146 
onConfigurationChangednull147     override fun onConfigurationChanged(newConfig: Configuration) {
148         LOGGER.v("Screensaver configuration changed")
149         super.onConfigurationChanged(newConfig)
150 
151         startPositionUpdater()
152     }
153 
setClockStylenull154     private fun setClockStyle() {
155         Utils.setScreensaverClockStyle(mDigitalClock!!, mAnalogClock!!)
156         val dimNightMode: Boolean = DataModel.dataModel.screensaverNightModeOn
157         Utils.dimClockView(dimNightMode, mMainClockView!!)
158         isScreenBright = !dimNightMode
159     }
160 
161     /**
162      * The [.mContentView] will be drawn shortly. When that draw occurs, the position updater
163      * callback will also be executed to choose a random position for the time display as well as
164      * schedule future callbacks to move the time display each minute.
165      */
startPositionUpdaternull166     private fun startPositionUpdater() {
167         mContentView?.viewTreeObserver?.addOnPreDrawListener(mStartPositionUpdater)
168     }
169 
170     /**
171      * This activity is no longer in the foreground; position callbacks should be removed.
172      */
stopPositionUpdaternull173     private fun stopPositionUpdater() {
174         mContentView?.viewTreeObserver?.removeOnPreDrawListener(mStartPositionUpdater)
175         mPositionUpdater?.stop()
176     }
177 
178     private inner class StartPositionUpdater : OnPreDrawListener {
179         /**
180          * This callback occurs after initial layout has completed. It is an appropriate place to
181          * select a random position for [.mMainClockView] and schedule future callbacks to update
182          * its position.
183          *
184          * @return `true` to continue with the drawing pass
185          */
onPreDrawnull186         override fun onPreDraw(): Boolean {
187             if (mContentView!!.viewTreeObserver.isAlive) {
188                 // (Re)start the periodic position updater.
189                 mPositionUpdater?.start()
190 
191                 // This listener must now be removed to avoid starting the position updater again.
192                 mContentView?.viewTreeObserver?.removeOnPreDrawListener(mStartPositionUpdater)
193             }
194             return true
195         }
196     }
197 
198     companion object {
199         private val LOGGER = LogUtils.Logger("Screensaver")
200     }
201 }