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