close
The Wayback Machine - https://web.archive.org/web/20171110014939/https://developer.android.com/samples/AlwaysOn/src/com.example.android.wearable.wear.alwayson/MainActivity.html
Skip to content

Most visited

Recently visited

navigation
AlwaysOn / src / com.example.android.wearable.wear.alwayson /

MainActivity.java

1
/*
2
 * Copyright (C) 2015 Google Inc. All Rights Reserved.
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
package com.example.android.wearable.wear.alwayson;
17
 
18
import android.app.AlarmManager;
19
import android.app.PendingIntent;
20
import android.content.Context;
21
import android.content.Intent;
22
import android.graphics.Color;
23
import android.os.Bundle;
24
import android.os.Handler;
25
import android.os.Message;
26
import android.support.wearable.activity.WearableActivity;
27
import android.support.wearable.view.WatchViewStub;
28
import android.util.Log;
29
import android.widget.TextView;
30
 
31
import java.lang.ref.WeakReference;
32
import java.text.SimpleDateFormat;
33
import java.util.Date;
34
import java.util.Locale;
35
import java.util.concurrent.TimeUnit;
36
 
37
/**
38
 * Demonstrates support for Ambient screens by extending WearableActivity and overriding
39
 * onEnterAmbient, onUpdateAmbient, and onExitAmbient.
40
 *
41
 * There are two modes (Active and Ambient). To trigger future updates (data/screen), we use a
42
 * custom Handler for the "Active" mode and an Alarm for the "Ambient" mode.
43
 *
44
 * Why don't we use just one? Handlers are generally less battery intensive and can be triggered
45
 * every second. However, they can not wake up the processor (common in Ambient mode).
46
 *
47
 * Alarms can wake up the processor (what we need for Ambient), but they struggle with quick updates
48
 * (less than one second) and are much less efficient compared to Handlers.
49
 *
50
 * Therefore, we use Handlers for "Active" mode (can trigger every second and are better on the
51
 * battery), and we use Alarms for "Ambient" mode (only need to update once every 20 seconds and
52
 * they can wake up a sleeping processor).
53
 *
54
 * Again, the Activity waits 20 seconds between doing any processing (getting data, updating screen
55
 * etc.) while in ambient mode to conserving battery life (processor allowed to sleep). If you can
56
 * hold off on updates for a full minute, you can throw away all the Alarm code and just use
57
 * onUpdateAmbient() to save even more battery life.
58
 *
59
 * As always, you will still want to apply the performance guidelines outlined in the Watch Faces
60
 * documention to your app.
61
 *
62
 * Finally, in ambient mode, this Activity follows the same best practices outlined in the
63
 * Watch Faces API documentation, e.g., keep most pixels black, avoid large blocks of white pixels,
64
 * use only black and white, and disable anti-aliasing.
65
 *
66
 */
67
public class MainActivity extends WearableActivity {
68
 
69
    private static final String TAG = "MainActivity";
70
 
71
    /** Custom 'what' for Message sent to Handler. */
72
    private static final int MSG_UPDATE_SCREEN = 0;
73
 
74
    /** Milliseconds between updates based on state. */
75
    private static final long ACTIVE_INTERVAL_MS = TimeUnit.SECONDS.toMillis(1);
76
    private static final long AMBIENT_INTERVAL_MS = TimeUnit.SECONDS.toMillis(20);
77
 
78
    /** Tracks latest ambient details, such as burnin offsets, etc. */
79
    private Bundle mAmbientDetails;
80
 
81
    private TextView mTimeTextView;
82
    private TextView mTimeStampTextView;
83
    private TextView mStateTextView;
84
    private TextView mUpdateRateTextView;
85
    private TextView mDrawCountTextView;
86
 
87
    private final SimpleDateFormat sDateFormat =
88
            new SimpleDateFormat("HH:mm:ss", Locale.US);
89
 
90
    private volatile int mDrawCount = 0;
91
 
92
 
93
    /**
94
     * Since the handler (used in active mode) can't wake up the processor when the device is in
95
     * ambient mode and undocked, we use an Alarm to cover ambient mode updates when we need them
96
     * more frequently than every minute. Remember, if getting updates once a minute in ambient
97
     * mode is enough, you can do away with the Alarm code and just rely on the onUpdateAmbient()
98
     * callback.
99
     */
100
    private AlarmManager mAmbientStateAlarmManager;
101
    private PendingIntent mAmbientStatePendingIntent;
102
 
103
    /**
104
     * This custom handler is used for updates in "Active" mode. We use a separate static class to
105
     * help us avoid memory leaks.
106
     */
107
    private final Handler mActiveModeUpdateHandler = new UpdateHandler(this);
108
 
109
    @Override
110
    public void onCreate(Bundle savedInstanceState) {
111
        Log.d(TAG, "onCreate()");
112
        super.onCreate(savedInstanceState);
113
 
114
        setContentView(R.layout.activity_main);
115
 
116
        setAmbientEnabled();
117
 
118
        mAmbientStateAlarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
119
        Intent ambientStateIntent = new Intent(getApplicationContext(), MainActivity.class);
120
 
121
        mAmbientStatePendingIntent = PendingIntent.getActivity(
122
                getApplicationContext(),
123
                0 /* requestCode */,
124
                ambientStateIntent,
125
                PendingIntent.FLAG_UPDATE_CURRENT);
126
 
127
 
128
        /** Determines whether watch is round or square and applies proper view. **/
129
        final WatchViewStub stub = (WatchViewStub) findViewById(R.id.watch_view_stub);
130
        stub.setOnLayoutInflatedListener(new WatchViewStub.OnLayoutInflatedListener() {
131
            @Override
132
            public void onLayoutInflated(WatchViewStub stub) {
133
 
134
                mTimeTextView = (TextView) stub.findViewById(R.id.time);
135
                mTimeStampTextView = (TextView) stub.findViewById(R.id.time_stamp);
136
                mStateTextView = (TextView) stub.findViewById(R.id.state);
137
                mUpdateRateTextView = (TextView) stub.findViewById(R.id.update_rate);
138
                mDrawCountTextView = (TextView) stub.findViewById(R.id.draw_count);
139
 
140
                refreshDisplayAndSetNextUpdate();
141
            }
142
        });
143
    }
144
 
145
    /**
146
     * This is mostly triggered by the Alarms we set in Ambient mode and informs us we need to
147
     * update the screen (and process any data).
148
     */
149
    @Override
150
    public void onNewIntent(Intent intent) {
151
        Log.d(TAG, "onNewIntent(): " + intent);
152
        super.onNewIntent(intent);
153
 
154
        setIntent(intent);
155
 
156
        refreshDisplayAndSetNextUpdate();
157
    }
158
 
159
    @Override
160
    public void onDestroy() {
161
        Log.d(TAG, "onDestroy()");
162
 
163
        mActiveModeUpdateHandler.removeMessages(MSG_UPDATE_SCREEN);
164
        mAmbientStateAlarmManager.cancel(mAmbientStatePendingIntent);
165
 
166
        super.onDestroy();
167
    }
168
 
169
    /**
170
     * Prepares UI for Ambient view.
171
     */
172
    @Override
173
    public void onEnterAmbient(Bundle ambientDetails) {
174
        Log.d(TAG, "onEnterAmbient()");
175
        super.onEnterAmbient(ambientDetails);
176
 
177
        /**
178
         * In this sample, we aren't using the ambient details bundle (EXTRA_BURN_IN_PROTECTION or
179
         * EXTRA_LOWBIT_AMBIENT), but if you need them, you can pull them from the local variable
180
         * set here.
181
         */
182
        mAmbientDetails = ambientDetails;
183
 
184
        /** Clears Handler queue (only needed for updates in active mode). */
185
        mActiveModeUpdateHandler.removeMessages(MSG_UPDATE_SCREEN);
186
 
187
        /**
188
         * Following best practices outlined in WatchFaces API (keeping most pixels black,
189
         * avoiding large blocks of white pixels, using only black and white,
190
         * and disabling anti-aliasing anti-aliasing, etc.)
191
         */
192
        mStateTextView.setTextColor(Color.WHITE);
193
        mUpdateRateTextView.setTextColor(Color.WHITE);
194
        mDrawCountTextView.setTextColor(Color.WHITE);
195
 
196
        mTimeTextView.getPaint().setAntiAlias(false);
197
        mTimeStampTextView.getPaint().setAntiAlias(false);
198
        mStateTextView.getPaint().setAntiAlias(false);
199
        mUpdateRateTextView.getPaint().setAntiAlias(false);
200
        mDrawCountTextView.getPaint().setAntiAlias(false);
201
 
202
        refreshDisplayAndSetNextUpdate();
203
    }
204
 
205
    /**
206
     * Updates UI in Ambient view (once a minute). Because we need to update UI sooner than that
207
     * (every ~20 seconds), we also use an Alarm. However, since the processor is awake for this
208
     * callback, we might as well call refreshDisplayAndSetNextUpdate() to update screen and reset
209
     * the Alarm.
210
     *
211
     * If you are happy with just updating the screen once a minute in Ambient Mode (which will be
212
     * the case a majority of the time), then you can just use this method and remove all
213
     * references/code regarding Alarms.
214
     */
215
    @Override
216
    public void onUpdateAmbient() {
217
        Log.d(TAG, "onUpdateAmbient()");
218
        super.onUpdateAmbient();
219
 
220
        refreshDisplayAndSetNextUpdate();
221
    }
222
 
223
    /**
224
     * Prepares UI for Active view (non-Ambient).
225
     */
226
    @Override
227
    public void onExitAmbient() {
228
        Log.d(TAG, "onExitAmbient()");
229
        super.onExitAmbient();
230
 
231
        /** Clears out Alarms since they are only used in ambient mode. */
232
        mAmbientStateAlarmManager.cancel(mAmbientStatePendingIntent);
233
 
234
        mStateTextView.setTextColor(Color.GREEN);
235
        mUpdateRateTextView.setTextColor(Color.GREEN);
236
        mDrawCountTextView.setTextColor(Color.GREEN);
237
 
238
        mTimeTextView.getPaint().setAntiAlias(true);
239
        mTimeStampTextView.getPaint().setAntiAlias(true);
240
        mStateTextView.getPaint().setAntiAlias(true);
241
        mUpdateRateTextView.getPaint().setAntiAlias(true);
242
        mDrawCountTextView.getPaint().setAntiAlias(true);
243
 
244
        refreshDisplayAndSetNextUpdate();
245
    }
246
 
247
    /**
248
     * Loads data/updates screen (via method), but most importantly, sets up the next refresh
249
     * (active mode = Handler and ambient mode = Alarm).
250
     */
251
    private void refreshDisplayAndSetNextUpdate() {
252
 
253
        loadDataAndUpdateScreen();
254
 
255
        long timeMs = System.currentTimeMillis();
256
 
257
        if (isAmbient()) {
258
            /** Calculate next trigger time (based on state). */
259
            long delayMs = AMBIENT_INTERVAL_MS - (timeMs % AMBIENT_INTERVAL_MS);
260
            long triggerTimeMs = timeMs + delayMs;
261
 
262
            /**
263
             * Note: Make sure you have set activity launchMode to singleInstance in the manifest.
264
             * Otherwise, it is easy for the AlarmManager launch intent to open a new activity
265
             * every time the Alarm is triggered rather than reusing this Activity.
266
             */
267
            mAmbientStateAlarmManager.setExact(
268
                    AlarmManager.RTC_WAKEUP,
269
                    triggerTimeMs,
270
                    mAmbientStatePendingIntent);
271
 
272
        } else {
273
            /** Calculate next trigger time (based on state). */
274
            long delayMs = ACTIVE_INTERVAL_MS - (timeMs % ACTIVE_INTERVAL_MS);
275
 
276
            mActiveModeUpdateHandler.removeMessages(MSG_UPDATE_SCREEN);
277
            mActiveModeUpdateHandler.sendEmptyMessageDelayed(MSG_UPDATE_SCREEN, delayMs);
278
        }
279
    }
280
 
281
    /**
282
     * Updates display based on Ambient state. If you need to pull data, you should do it here.
283
     */
284
    private void loadDataAndUpdateScreen() {
285
 
286
        mDrawCount += 1;
287
        long currentTimeMs = System.currentTimeMillis();
288
        Log.d(TAG, "loadDataAndUpdateScreen(): " + currentTimeMs + "(" + isAmbient() + ")");
289
 
290
        if (isAmbient()) {
291
 
292
            mTimeTextView.setText(sDateFormat.format(new Date()));
293
            mTimeStampTextView.setText(getString(R.string.timestamp_label, currentTimeMs));
294
 
295
            mStateTextView.setText(getString(R.string.mode_ambient_label));
296
            mUpdateRateTextView.setText(
297
                    getString(R.string.update_rate_label, (AMBIENT_INTERVAL_MS / 1000)));
298
 
299
            mDrawCountTextView.setText(getString(R.string.draw_count_label, mDrawCount));
300
 
301
        } else {
302
            mTimeTextView.setText(sDateFormat.format(new Date()));
303
            mTimeStampTextView.setText(getString(R.string.timestamp_label, currentTimeMs));
304
 
305
            mStateTextView.setText(getString(R.string.mode_active_label));
306
            mUpdateRateTextView.setText(
307
                    getString(R.string.update_rate_label, (ACTIVE_INTERVAL_MS / 1000)));
308
 
309
            mDrawCountTextView.setText(getString(R.string.draw_count_label, mDrawCount));
310
        }
311
    }
312
 
313
    /**
314
     * Handler separated into static class to avoid memory leaks.
315
     */
316
    private static class UpdateHandler extends Handler {
317
        private final WeakReference<MainActivity> mMainActivityWeakReference;
318
 
319
        public UpdateHandler(MainActivity reference) {
320
            mMainActivityWeakReference = new WeakReference<MainActivity>(reference);
321
        }
322
 
323
        @Override
324
        public void handleMessage(Message message) {
325
            MainActivity mainActivity = mMainActivityWeakReference.get();
326
 
327
            if (mainActivity != null) {
328
                switch (message.what) {
329
                    case MSG_UPDATE_SCREEN:
330
                        mainActivity.refreshDisplayAndSetNextUpdate();
331
                        break;
332
                }
333
            }
334
        }
335
    }
336
}
This site uses cookies to store your preferences for site-specific language and display options.

Get the latest Android developer news and tips that will help you find success on Google Play.

* Required Fields

Hooray!

Follow Google Developers on WeChat

BERJAYA

Browse this site in ?

You requested a page in , but your language preference for this site is .

Would you like to change your language preference and browse this site in ? If you want to change your language preference later, use the language menu at the bottom of each page.

This class requires API level or higher

This doc is hidden because your selected API level for the documentation is . You can change the documentation API level with the selector above the left navigation.

For more information about specifying the API level your app requires, read Supporting Different Platform Versions.

Take a short survey?
Help us improve the Android developer experience.
(Sep 2017 survey)