1c57490dff5bfbf601d4b708fdae029df99f807b2Daniel Sandler/*
2d6d68b49aaf5b0fbcce89097ca4c02d73bcea4ddJustin Klaassen * Copyright (C) 2016 The Android Open Source Project
3c57490dff5bfbf601d4b708fdae029df99f807b2Daniel Sandler *
4c57490dff5bfbf601d4b708fdae029df99f807b2Daniel Sandler * Licensed under the Apache License, Version 2.0 (the "License");
5c57490dff5bfbf601d4b708fdae029df99f807b2Daniel Sandler * you may not use this file except in compliance with the License.
6c57490dff5bfbf601d4b708fdae029df99f807b2Daniel Sandler * You may obtain a copy of the License at
7c57490dff5bfbf601d4b708fdae029df99f807b2Daniel Sandler *
8d6d68b49aaf5b0fbcce89097ca4c02d73bcea4ddJustin Klaassen *     http://www.apache.org/licenses/LICENSE-2.0
9c57490dff5bfbf601d4b708fdae029df99f807b2Daniel Sandler *
10c57490dff5bfbf601d4b708fdae029df99f807b2Daniel Sandler * Unless required by applicable law or agreed to in writing, software
11c57490dff5bfbf601d4b708fdae029df99f807b2Daniel Sandler * distributed under the License is distributed on an "AS IS" BASIS,
12c57490dff5bfbf601d4b708fdae029df99f807b2Daniel Sandler * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13c57490dff5bfbf601d4b708fdae029df99f807b2Daniel Sandler * See the License for the specific language governing permissions and
14c57490dff5bfbf601d4b708fdae029df99f807b2Daniel Sandler * limitations under the License.
15c57490dff5bfbf601d4b708fdae029df99f807b2Daniel Sandler */
16c57490dff5bfbf601d4b708fdae029df99f807b2Daniel Sandler
17c57490dff5bfbf601d4b708fdae029df99f807b2Daniel Sandlerpackage com.android.deskclock;
18c57490dff5bfbf601d4b708fdae029df99f807b2Daniel Sandler
19c0743272ad924bc97d335d695be3e6b32a83dd7dAdrian Roosimport android.app.AlarmManager;
20ed2084787cec0f02e6cead215d409d6f2f60f737Alon Albertimport android.content.BroadcastReceiver;
21ed2084787cec0f02e6cead215d409d6f2f60f737Alon Albertimport android.content.Context;
22ed2084787cec0f02e6cead215d409d6f2f60f737Alon Albertimport android.content.Intent;
23ed2084787cec0f02e6cead215d409d6f2f60f737Alon Albertimport android.content.IntentFilter;
2490dc136d444ba29fe8db6b20872022b18f18dc94John Spurlockimport android.content.res.Configuration;
2540e4b8c5bda04416feaa3f41539c241479d4f046Annie Chinimport android.database.ContentObserver;
26c624a3fb698c13312a5e14114c37f45e3b3438bcJustin Klaassenimport android.net.Uri;
2790dc136d444ba29fe8db6b20872022b18f18dc94John Spurlockimport android.os.Handler;
2840e4b8c5bda04416feaa3f41539c241479d4f046Annie Chinimport android.provider.Settings;
2917b4ca405a8b0d154f4d8354ab62044cd25b2204Daniel Sandlerimport android.service.dreams.DreamService;
30c57490dff5bfbf601d4b708fdae029df99f807b2Daniel Sandlerimport android.view.View;
313af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieuximport android.view.ViewTreeObserver.OnPreDrawListener;
323a4ba0db218b830af3dd17fde2952125a2e50fdcIsaac Katzenelsonimport android.widget.TextClock;
33c57490dff5bfbf601d4b708fdae029df99f807b2Daniel Sandler
343af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieuximport com.android.deskclock.data.DataModel;
353af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieuximport com.android.deskclock.uidata.UiDataModel;
3678b8e1513e24c58ffea6ee4edbebdce85c248f6fSam Blitzstein
373af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieuxpublic final class Screensaver extends DreamService {
38f10aaa263d364ecca5c6f3ee39ef96faf9eac83dBudi Kusmiantoro
39310210dab3e97b1defe0870ecbc25b1451e90392Justin Klaassen    private static final LogUtils.Logger LOGGER = new LogUtils.Logger("Screensaver");
40f10aaa263d364ecca5c6f3ee39ef96faf9eac83dBudi Kusmiantoro
413af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux    private final OnPreDrawListener mStartPositionUpdater = new StartPositionUpdater();
423af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux    private MoveScreensaverRunnable mPositionUpdater;
43c57490dff5bfbf601d4b708fdae029df99f807b2Daniel Sandler
44c5b45b80a280794ca131b9857c5885c337d4d6e1Isaac Katzenelson    private String mDateFormat;
45c5b45b80a280794ca131b9857c5885c337d4d6e1Isaac Katzenelson    private String mDateFormatForAccessibility;
46e269bd8658721a71fd9d42084b280042c5258945Daniel Sandler
47b8c4512b4a6d3e78d426e7e67b502e4735e365cfJustin Klaassen    private View mContentView;
48b8c4512b4a6d3e78d426e7e67b502e4735e365cfJustin Klaassen    private View mMainClockView;
49b8c4512b4a6d3e78d426e7e67b502e4735e365cfJustin Klaassen    private TextClock mDigitalClock;
50b8c4512b4a6d3e78d426e7e67b502e4735e365cfJustin Klaassen    private AnalogClock mAnalogClock;
51b8c4512b4a6d3e78d426e7e67b502e4735e365cfJustin Klaassen
5240e4b8c5bda04416feaa3f41539c241479d4f046Annie Chin    /* Register ContentObserver to see alarm changes for pre-L */
53d6d68b49aaf5b0fbcce89097ca4c02d73bcea4ddJustin Klaassen    private final ContentObserver mSettingsContentObserver =
54d6d68b49aaf5b0fbcce89097ca4c02d73bcea4ddJustin Klaassen            Utils.isLOrLater() ? null : new ContentObserver(new Handler()) {
55d6d68b49aaf5b0fbcce89097ca4c02d73bcea4ddJustin Klaassen                @Override
56d6d68b49aaf5b0fbcce89097ca4c02d73bcea4ddJustin Klaassen                public void onChange(boolean selfChange) {
57d6d68b49aaf5b0fbcce89097ca4c02d73bcea4ddJustin Klaassen                    Utils.refreshAlarm(Screensaver.this, mContentView);
58d6d68b49aaf5b0fbcce89097ca4c02d73bcea4ddJustin Klaassen                }
59d6d68b49aaf5b0fbcce89097ca4c02d73bcea4ddJustin Klaassen            };
6040e4b8c5bda04416feaa3f41539c241479d4f046Annie Chin
613af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux    // Runs every midnight or when the time changes and refreshes the date.
62eb3a1f0714e209a8335d84142778465aa6b44c5cRobyn Coultas    private final Runnable mMidnightUpdater = new Runnable() {
63eb3a1f0714e209a8335d84142778465aa6b44c5cRobyn Coultas        @Override
64eb3a1f0714e209a8335d84142778465aa6b44c5cRobyn Coultas        public void run() {
65eb3a1f0714e209a8335d84142778465aa6b44c5cRobyn Coultas            Utils.updateDate(mDateFormat, mDateFormatForAccessibility, mContentView);
66eb3a1f0714e209a8335d84142778465aa6b44c5cRobyn Coultas        }
67eb3a1f0714e209a8335d84142778465aa6b44c5cRobyn Coultas    };
68eb3a1f0714e209a8335d84142778465aa6b44c5cRobyn Coultas
69eb3a1f0714e209a8335d84142778465aa6b44c5cRobyn Coultas    /**
703af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux     * Receiver to alarm clock changes.
71eb3a1f0714e209a8335d84142778465aa6b44c5cRobyn Coultas     */
723af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux    private final BroadcastReceiver mAlarmChangedReceiver = new BroadcastReceiver() {
73eb3a1f0714e209a8335d84142778465aa6b44c5cRobyn Coultas        @Override
74eb3a1f0714e209a8335d84142778465aa6b44c5cRobyn Coultas        public void onReceive(Context context, Intent intent) {
753af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux            Utils.refreshAlarm(Screensaver.this, mContentView);
76eb3a1f0714e209a8335d84142778465aa6b44c5cRobyn Coultas        }
77eb3a1f0714e209a8335d84142778465aa6b44c5cRobyn Coultas    };
78eb3a1f0714e209a8335d84142778465aa6b44c5cRobyn Coultas
7990dc136d444ba29fe8db6b20872022b18f18dc94John Spurlock    @Override
8090dc136d444ba29fe8db6b20872022b18f18dc94John Spurlock    public void onCreate() {
81310210dab3e97b1defe0870ecbc25b1451e90392Justin Klaassen        LOGGER.v("Screensaver created");
82b8c4512b4a6d3e78d426e7e67b502e4735e365cfJustin Klaassen
83b8c4512b4a6d3e78d426e7e67b502e4735e365cfJustin Klaassen        setTheme(R.style.Theme_DeskClock);
8490dc136d444ba29fe8db6b20872022b18f18dc94John Spurlock        super.onCreate();
85c5b45b80a280794ca131b9857c5885c337d4d6e1Isaac Katzenelson
86c5b45b80a280794ca131b9857c5885c337d4d6e1Isaac Katzenelson        mDateFormat = getString(R.string.abbrev_wday_month_day_no_year);
87c5b45b80a280794ca131b9857c5885c337d4d6e1Isaac Katzenelson        mDateFormatForAccessibility = getString(R.string.full_wday_month_day_no_year);
889a1fd04f15b653a6600629aee41c7d3fd7d843b3Daniel Sandler    }
899a1fd04f15b653a6600629aee41c7d3fd7d843b3Daniel Sandler
90e269bd8658721a71fd9d42084b280042c5258945Daniel Sandler    @Override
9190dc136d444ba29fe8db6b20872022b18f18dc94John Spurlock    public void onAttachedToWindow() {
92310210dab3e97b1defe0870ecbc25b1451e90392Justin Klaassen        LOGGER.v("Screensaver attached to window");
9390dc136d444ba29fe8db6b20872022b18f18dc94John Spurlock        super.onAttachedToWindow();
94c57490dff5bfbf601d4b708fdae029df99f807b2Daniel Sandler
953af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux        setContentView(R.layout.desk_clock_saver);
96b8c4512b4a6d3e78d426e7e67b502e4735e365cfJustin Klaassen
97b8c4512b4a6d3e78d426e7e67b502e4735e365cfJustin Klaassen        mContentView = findViewById(R.id.saver_container);
98b8c4512b4a6d3e78d426e7e67b502e4735e365cfJustin Klaassen        mMainClockView = mContentView.findViewById(R.id.main_clock);
99b8c4512b4a6d3e78d426e7e67b502e4735e365cfJustin Klaassen        mDigitalClock = (TextClock) mMainClockView.findViewById(R.id.digital_clock);
100b8c4512b4a6d3e78d426e7e67b502e4735e365cfJustin Klaassen        mAnalogClock = (AnalogClock) mMainClockView.findViewById(R.id.analog_clock);
101b8c4512b4a6d3e78d426e7e67b502e4735e365cfJustin Klaassen
102b8c4512b4a6d3e78d426e7e67b502e4735e365cfJustin Klaassen        setClockStyle();
103b8c4512b4a6d3e78d426e7e67b502e4735e365cfJustin Klaassen        Utils.setClockIconTypeface(mContentView);
104b8c4512b4a6d3e78d426e7e67b502e4735e365cfJustin Klaassen        Utils.setTimeFormat(mDigitalClock, false);
105b8c4512b4a6d3e78d426e7e67b502e4735e365cfJustin Klaassen        mAnalogClock.enableSeconds(false);
106b8c4512b4a6d3e78d426e7e67b502e4735e365cfJustin Klaassen
107de7bc34a9e062b175343ac464440a4d6eaceaab6Sean Stout        mContentView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE
108de7bc34a9e062b175343ac464440a4d6eaceaab6Sean Stout                | View.SYSTEM_UI_FLAG_IMMERSIVE
109de7bc34a9e062b175343ac464440a4d6eaceaab6Sean Stout                | View.SYSTEM_UI_FLAG_FULLSCREEN
110de7bc34a9e062b175343ac464440a4d6eaceaab6Sean Stout                | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
111de7bc34a9e062b175343ac464440a4d6eaceaab6Sean Stout                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
1123af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux
113b8c4512b4a6d3e78d426e7e67b502e4735e365cfJustin Klaassen        mPositionUpdater = new MoveScreensaverRunnable(mContentView, mMainClockView);
1143af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux
11557ca681913fa5b9cd86a7342a91cfe4cc5b910dfDaniel Sandler        // We want the screen saver to exit upon user interaction.
11657ca681913fa5b9cd86a7342a91cfe4cc5b910dfDaniel Sandler        setInteractive(false);
1173d6adf080cb4a1469244e393807f6921b9f1149bJohn Spurlock        setFullscreen(true);
1181a7820902f3a6428f0bb586f8f1b5a2824838cb0Daniel Sandler
119eb3a1f0714e209a8335d84142778465aa6b44c5cRobyn Coultas        // Setup handlers for time reference changes and date updates.
120b8c4512b4a6d3e78d426e7e67b502e4735e365cfJustin Klaassen        if (Utils.isLOrLater()) {
121b8c4512b4a6d3e78d426e7e67b502e4735e365cfJustin Klaassen            registerReceiver(mAlarmChangedReceiver,
122b8c4512b4a6d3e78d426e7e67b502e4735e365cfJustin Klaassen                    new IntentFilter(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED));
123b8c4512b4a6d3e78d426e7e67b502e4735e365cfJustin Klaassen        }
124eb3a1f0714e209a8335d84142778465aa6b44c5cRobyn Coultas
1253af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux        if (mSettingsContentObserver != null) {
126c624a3fb698c13312a5e14114c37f45e3b3438bcJustin Klaassen            @SuppressWarnings("deprecation")
127c624a3fb698c13312a5e14114c37f45e3b3438bcJustin Klaassen            final Uri uri = Settings.System.getUriFor(Settings.System.NEXT_ALARM_FORMATTED);
128c624a3fb698c13312a5e14114c37f45e3b3438bcJustin Klaassen            getContentResolver().registerContentObserver(uri, false, mSettingsContentObserver);
12940e4b8c5bda04416feaa3f41539c241479d4f046Annie Chin        }
13040e4b8c5bda04416feaa3f41539c241479d4f046Annie Chin
1313af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux        Utils.updateDate(mDateFormat, mDateFormatForAccessibility, mContentView);
132b8c4512b4a6d3e78d426e7e67b502e4735e365cfJustin Klaassen        Utils.refreshAlarm(this, mContentView);
1333af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux
1343af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux        startPositionUpdater();
1353af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux        UiDataModel.getUiDataModel().addMidnightCallback(mMidnightUpdater, 100);
136c57490dff5bfbf601d4b708fdae029df99f807b2Daniel Sandler    }
137c57490dff5bfbf601d4b708fdae029df99f807b2Daniel Sandler
138c57490dff5bfbf601d4b708fdae029df99f807b2Daniel Sandler    @Override
13990dc136d444ba29fe8db6b20872022b18f18dc94John Spurlock    public void onDetachedFromWindow() {
140310210dab3e97b1defe0870ecbc25b1451e90392Justin Klaassen        LOGGER.v("Screensaver detached from window");
14190dc136d444ba29fe8db6b20872022b18f18dc94John Spurlock        super.onDetachedFromWindow();
14290dc136d444ba29fe8db6b20872022b18f18dc94John Spurlock
1433af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux        if (mSettingsContentObserver != null) {
14440e4b8c5bda04416feaa3f41539c241479d4f046Annie Chin            getContentResolver().unregisterContentObserver(mSettingsContentObserver);
14540e4b8c5bda04416feaa3f41539c241479d4f046Annie Chin        }
14640e4b8c5bda04416feaa3f41539c241479d4f046Annie Chin
1473af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux        UiDataModel.getUiDataModel().removePeriodicCallback(mMidnightUpdater);
1483af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux        stopPositionUpdater();
1493af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux
150eb3a1f0714e209a8335d84142778465aa6b44c5cRobyn Coultas        // Tear down handlers for time reference changes and date updates.
151b8c4512b4a6d3e78d426e7e67b502e4735e365cfJustin Klaassen        if (Utils.isLOrLater()) {
152b8c4512b4a6d3e78d426e7e67b502e4735e365cfJustin Klaassen            unregisterReceiver(mAlarmChangedReceiver);
153b8c4512b4a6d3e78d426e7e67b502e4735e365cfJustin Klaassen        }
1543af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux    }
1553af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux
1563af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux    @Override
1573af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux    public void onConfigurationChanged(Configuration newConfig) {
158310210dab3e97b1defe0870ecbc25b1451e90392Justin Klaassen        LOGGER.v("Screensaver configuration changed");
1593af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux        super.onConfigurationChanged(newConfig);
1603af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux
161696a2dfdf1418addb286861c8a70223ce7c31431Sean Stout        startPositionUpdater();
162c57490dff5bfbf601d4b708fdae029df99f807b2Daniel Sandler    }
163c57490dff5bfbf601d4b708fdae029df99f807b2Daniel Sandler
1644560461b08b1660fa5776523d8344df0d8d23f1dItzhak Katzenelson    private void setClockStyle() {
16534142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux        Utils.setScreensaverClockStyle(mDigitalClock, mAnalogClock);
1663af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux        final boolean dimNightMode = DataModel.getDataModel().getScreensaverNightModeOn();
167b8c4512b4a6d3e78d426e7e67b502e4735e365cfJustin Klaassen        Utils.dimClockView(dimNightMode, mMainClockView);
16878b8e1513e24c58ffea6ee4edbebdce85c248f6fSam Blitzstein        setScreenBright(!dimNightMode);
1698f873a2bca1277f37cc8d08655d73385e5508232Daniel Sandler    }
1709a1fd04f15b653a6600629aee41c7d3fd7d843b3Daniel Sandler
1713af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux    /**
1723af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux     * The {@link #mContentView} will be drawn shortly. When that draw occurs, the position updater
1733af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux     * callback will also be executed to choose a random position for the time display as well as
1743af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux     * schedule future callbacks to move the time display each minute.
1753af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux     */
1763af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux    private void startPositionUpdater() {
177696a2dfdf1418addb286861c8a70223ce7c31431Sean Stout        if (mContentView != null) {
178696a2dfdf1418addb286861c8a70223ce7c31431Sean Stout            mContentView.getViewTreeObserver().addOnPreDrawListener(mStartPositionUpdater);
179696a2dfdf1418addb286861c8a70223ce7c31431Sean Stout        }
1803af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux    }
1813a4ba0db218b830af3dd17fde2952125a2e50fdcIsaac Katzenelson
1823af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux    /**
1833af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux     * This activity is no longer in the foreground; position callbacks should be removed.
1843af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux     */
1853af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux    private void stopPositionUpdater() {
186696a2dfdf1418addb286861c8a70223ce7c31431Sean Stout        if (mContentView != null) {
187696a2dfdf1418addb286861c8a70223ce7c31431Sean Stout            mContentView.getViewTreeObserver().removeOnPreDrawListener(mStartPositionUpdater);
188696a2dfdf1418addb286861c8a70223ce7c31431Sean Stout        }
1893af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux        mPositionUpdater.stop();
1903af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux    }
19178b8e1513e24c58ffea6ee4edbebdce85c248f6fSam Blitzstein
1923af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux    private final class StartPositionUpdater implements OnPreDrawListener {
1933af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux        /**
1943af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux         * This callback occurs after initial layout has completed. It is an appropriate place to
195b8c4512b4a6d3e78d426e7e67b502e4735e365cfJustin Klaassen         * select a random position for {@link #mMainClockView} and schedule future callbacks to update
1963af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux         * its position.
1973af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux         *
1983af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux         * @return {@code true} to continue with the drawing pass
1993af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux         */
2003af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux        @Override
2013af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux        public boolean onPreDraw() {
2023af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux            if (mContentView.getViewTreeObserver().isAlive()) {
2033af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux                // (Re)start the periodic position updater.
2043af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux                mPositionUpdater.start();
205c5b45b80a280794ca131b9857c5885c337d4d6e1Isaac Katzenelson
2063af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux                // This listener must now be removed to avoid starting the position updater again.
2073af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux                mContentView.getViewTreeObserver().removeOnPreDrawListener(mStartPositionUpdater);
2083af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux            }
2093af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux            return true;
2103af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux        }
21190dc136d444ba29fe8db6b20872022b18f18dc94John Spurlock    }
212310210dab3e97b1defe0870ecbc25b1451e90392Justin Klaassen}
213