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