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
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.database.ContentObserver;
25import android.net.Uri;
26import android.os.Bundle;
27import android.os.Handler;
28import android.provider.Settings;
29import android.view.View;
30import android.view.ViewTreeObserver.OnPreDrawListener;
31import android.view.Window;
32import android.view.WindowManager;
33import android.widget.TextClock;
34
35import com.android.deskclock.events.Events;
36import com.android.deskclock.uidata.UiDataModel;
37
38import static android.content.Intent.ACTION_BATTERY_CHANGED;
39import static android.os.BatteryManager.EXTRA_PLUGGED;
40
41public 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
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
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
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, 100);
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
174    public void onPause() {
175        super.onPause();
176        UiDataModel.getUiDataModel().removePeriodicCallback(mMidnightUpdater);
177        stopPositionUpdater();
178    }
179
180    @Override
181    public void onStop() {
182        if (mSettingsContentObserver != null) {
183            getContentResolver().unregisterContentObserver(mSettingsContentObserver);
184        }
185        unregisterReceiver(mIntentReceiver);
186        super.onStop();
187    }
188
189    @Override
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     */
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     */
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     */
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
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
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