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 }