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 }