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