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