diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 029a49d..fb305ef 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,4 @@ - - +--> + package="com.example.android.sunshine.app"> @@ -30,17 +29,19 @@ + + + android:supportsRtl="true" + android:theme="@style/AppTheme"> + android:theme="@style/SettingsTheme"> @@ -77,7 +78,7 @@ android:syncable="true" /> - + @@ -90,7 +91,7 @@ + android:exported="true"> @@ -103,49 +104,60 @@ + android:permission="com.google.android.c2dm.permission.SEND"> + - + android:label="@string/app_name"> - + + + android:label="@string/title_widget_today"> - + + android:enabled="@bool/widget_detail_enabled" + android:label="@string/title_widget_detail"> - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wear/src/main/java/com/example/android/sunshine/wear/SunshineWatchFaceService.java b/wear/src/main/java/com/example/android/sunshine/wear/SunshineWatchFaceService.java new file mode 100644 index 0000000..995edf7 --- /dev/null +++ b/wear/src/main/java/com/example/android/sunshine/wear/SunshineWatchFaceService.java @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.sunshine.wear; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.support.wearable.watchface.CanvasWatchFaceService; +import android.support.wearable.watchface.WatchFaceStyle; +import android.text.format.Time; +import android.view.SurfaceHolder; +import android.view.WindowInsets; + +import java.lang.ref.WeakReference; +import java.util.TimeZone; +import java.util.concurrent.TimeUnit; + +/** + * Digital watch face with seconds. In ambient mode, the seconds aren't displayed. On devices with + * low-bit ambient mode, the text is drawn without anti-aliasing in ambient mode. + */ +public class SunshineWatchFaceService extends CanvasWatchFaceService { + private static final Typeface NORMAL_TYPEFACE = + Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL); + + /** + * Update rate in milliseconds for interactive mode. We update once a second since seconds are + * displayed in interactive mode. + */ + private static final long INTERACTIVE_UPDATE_RATE_MS = TimeUnit.SECONDS.toMillis(1); + + /** + * Handler message id for updating the time periodically in interactive mode. + */ + private static final int MSG_UPDATE_TIME = 0; + + @Override + public SunshineWatchFaceEngine onCreateEngine() { + return new SunshineWatchFaceEngine(); + } + + private static class EngineHandler extends Handler { + private final WeakReference mWeakReference; + + public EngineHandler(SunshineWatchFaceEngine reference) { + mWeakReference = new WeakReference<>(reference); + } + + @Override + public void handleMessage(Message msg) { + SunshineWatchFaceEngine engine = mWeakReference.get(); + if (engine != null) { + switch (msg.what) { + case MSG_UPDATE_TIME: + engine.handleUpdateTimeMessage(); + break; + } + } + } + } + + private class SunshineWatchFaceEngine extends CanvasWatchFaceService.Engine { + final Handler mUpdateTimeHandler = new EngineHandler(this); + boolean mRegisteredTimeZoneReceiver = false; + Paint mBackgroundPaint; + Paint mTextPaint; + boolean mAmbient; + Time mTime; + final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + mTime.clear(intent.getStringExtra("time-zone")); + mTime.setToNow(); + } + }; + int mTapCount; + + float mXOffset; + float mYOffset; + + /** + * Whether the display supports fewer bits for each color in ambient mode. When true, we + * disable anti-aliasing in ambient mode. + */ + boolean mLowBitAmbient; + + @Override + public void onCreate(SurfaceHolder holder) { + super.onCreate(holder); + + setWatchFaceStyle(new WatchFaceStyle.Builder(SunshineWatchFaceService.this) + .setCardPeekMode(WatchFaceStyle.PEEK_MODE_VARIABLE) + .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE) + .setShowSystemUiTime(false) + .setAcceptsTapEvents(true) + .build()); + Resources resources = SunshineWatchFaceService.this.getResources(); + mYOffset = resources.getDimension(R.dimen.digital_y_offset); + + mBackgroundPaint = new Paint(); + mBackgroundPaint.setColor(resources.getColor(R.color.background)); + + mTextPaint = new Paint(); + mTextPaint = createTextPaint(resources.getColor(R.color.digital_text)); + + mTime = new Time(); + } + + @Override + public void onDestroy() { + mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME); + super.onDestroy(); + } + + private Paint createTextPaint(int textColor) { + Paint paint = new Paint(); + paint.setColor(textColor); + paint.setTypeface(NORMAL_TYPEFACE); + paint.setAntiAlias(true); + return paint; + } + + @Override + public void onVisibilityChanged(boolean visible) { + super.onVisibilityChanged(visible); + + if (visible) { + registerReceiver(); + + // Update time zone in case it changed while we weren't visible. + mTime.clear(TimeZone.getDefault().getID()); + mTime.setToNow(); + } else { + unregisterReceiver(); + } + + // Whether the timer should be running depends on whether we're visible (as well as + // whether we're in ambient mode), so we may need to start or stop the timer. + updateTimer(); + } + + private void registerReceiver() { + if (mRegisteredTimeZoneReceiver) { + return; + } + mRegisteredTimeZoneReceiver = true; + IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED); + SunshineWatchFaceService.this.registerReceiver(mTimeZoneReceiver, filter); + } + + private void unregisterReceiver() { + if (!mRegisteredTimeZoneReceiver) { + return; + } + mRegisteredTimeZoneReceiver = false; + SunshineWatchFaceService.this.unregisterReceiver(mTimeZoneReceiver); + } + + @Override + public void onApplyWindowInsets(WindowInsets insets) { + super.onApplyWindowInsets(insets); + + // Load resources that have alternate values for round watches. + Resources resources = SunshineWatchFaceService.this.getResources(); + boolean isRound = insets.isRound(); + mXOffset = resources.getDimension(isRound + ? R.dimen.digital_x_offset_round : R.dimen.digital_x_offset); + float textSize = resources.getDimension(isRound + ? R.dimen.digital_text_size_round : R.dimen.digital_text_size); + + mTextPaint.setTextSize(textSize); + } + + @Override + public void onPropertiesChanged(Bundle properties) { + super.onPropertiesChanged(properties); + mLowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false); + } + + @Override + public void onTimeTick() { + super.onTimeTick(); + invalidate(); + } + + @Override + public void onAmbientModeChanged(boolean inAmbientMode) { + super.onAmbientModeChanged(inAmbientMode); + if (mAmbient != inAmbientMode) { + mAmbient = inAmbientMode; + if (mLowBitAmbient) { + mTextPaint.setAntiAlias(!inAmbientMode); + } + invalidate(); + } + + // Whether the timer should be running depends on whether we're visible (as well as + // whether we're in ambient mode), so we may need to start or stop the timer. + updateTimer(); + } + + /** + * Captures tap event (and tap type) and toggles the background color if the user finishes + * a tap. + */ + @Override + public void onTapCommand(int tapType, int x, int y, long eventTime) { + Resources resources = SunshineWatchFaceService.this.getResources(); + switch (tapType) { + case TAP_TYPE_TOUCH: + // The user has started touching the screen. + break; + case TAP_TYPE_TOUCH_CANCEL: + // The user has started a different gesture or otherwise cancelled the tap. + break; + case TAP_TYPE_TAP: + // The user has completed the tap gesture. + mTapCount++; + mBackgroundPaint.setColor(resources.getColor(mTapCount % 2 == 0 ? + R.color.background : R.color.background2)); + break; + } + invalidate(); + } + + @Override + public void onDraw(Canvas canvas, Rect bounds) { + // Draw the background. + if (isInAmbientMode()) { + canvas.drawColor(Color.BLACK); + } else { + canvas.drawRect(0, 0, bounds.width(), bounds.height(), mBackgroundPaint); + } + + // Draw H:MM in ambient mode or H:MM:SS in interactive mode. + mTime.setToNow(); + String text = mAmbient + ? String.format("%d:%02d", mTime.hour, mTime.minute) + : String.format("%d:%02d:%02d", mTime.hour, mTime.minute, mTime.second); + canvas.drawText(text, mXOffset, mYOffset, mTextPaint); + } + + /** + * Starts the {@link #mUpdateTimeHandler} timer if it should be running and isn't currently + * or stops it if it shouldn't be running but currently is. + */ + private void updateTimer() { + mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME); + if (shouldTimerBeRunning()) { + mUpdateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME); + } + } + + /** + * Returns whether the {@link #mUpdateTimeHandler} timer should be running. The timer should + * only run when we're visible and in interactive mode. + */ + private boolean shouldTimerBeRunning() { + return isVisible() && !isInAmbientMode(); + } + + /** + * Handle updating the time periodically in interactive mode. + */ + private void handleUpdateTimeMessage() { + invalidate(); + if (shouldTimerBeRunning()) { + long timeMs = System.currentTimeMillis(); + long delayMs = INTERACTIVE_UPDATE_RATE_MS + - (timeMs % INTERACTIVE_UPDATE_RATE_MS); + mUpdateTimeHandler.sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs); + } + } + } +} diff --git a/wear/src/main/res/drawable-nodpi/preview_digital.png b/wear/src/main/res/drawable-nodpi/preview_digital.png new file mode 100644 index 0000000..1123d1c Binary files /dev/null and b/wear/src/main/res/drawable-nodpi/preview_digital.png differ diff --git a/wear/src/main/res/drawable-nodpi/preview_digital_circular.png b/wear/src/main/res/drawable-nodpi/preview_digital_circular.png new file mode 100644 index 0000000..997c8cf Binary files /dev/null and b/wear/src/main/res/drawable-nodpi/preview_digital_circular.png differ diff --git a/wear/src/main/res/mipmap-hdpi/ic_launcher.png b/wear/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..cde69bc Binary files /dev/null and b/wear/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/wear/src/main/res/mipmap-mdpi/ic_launcher.png b/wear/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..c133a0c Binary files /dev/null and b/wear/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/wear/src/main/res/mipmap-xhdpi/ic_launcher.png b/wear/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..bfa42f0 Binary files /dev/null and b/wear/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/wear/src/main/res/mipmap-xxhdpi/ic_launcher.png b/wear/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..324e72c Binary files /dev/null and b/wear/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/wear/src/main/res/values/colors.xml b/wear/src/main/res/values/colors.xml new file mode 100644 index 0000000..45006f1 --- /dev/null +++ b/wear/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #000000 + #000088 + #ffffff + diff --git a/wear/src/main/res/values/dimens.xml b/wear/src/main/res/values/dimens.xml new file mode 100644 index 0000000..683f561 --- /dev/null +++ b/wear/src/main/res/values/dimens.xml @@ -0,0 +1,8 @@ + + + 40dp + 45dp + 15dp + 25dp + 90dp + diff --git a/wear/src/main/res/values/strings.xml b/wear/src/main/res/values/strings.xml new file mode 100644 index 0000000..6b2f158 --- /dev/null +++ b/wear/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + Sunshine + Weather + diff --git a/wear/src/main/res/xml/watch_face.xml b/wear/src/main/res/xml/watch_face.xml new file mode 100644 index 0000000..11a664b --- /dev/null +++ b/wear/src/main/res/xml/watch_face.xml @@ -0,0 +1,2 @@ + +