1 /*
2  * Copyright (C) 2014 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 
17 package com.example.android.wearable.watchface;
18 
19 import android.animation.AnimatorSet;
20 import android.animation.ObjectAnimator;
21 import android.app.Activity;
22 import android.content.Context;
23 import android.graphics.Color;
24 import android.os.Bundle;
25 import android.support.v7.widget.LinearLayoutManager;
26 import android.support.v7.widget.RecyclerView;
27 import android.support.wearable.view.BoxInsetLayout;
28 import android.support.wearable.view.CircledImageView;
29 import android.support.wearable.view.WearableListView;
30 import android.util.Log;
31 import android.view.LayoutInflater;
32 import android.view.View;
33 import android.view.ViewGroup;
34 import android.view.WindowInsets;
35 import android.widget.LinearLayout;
36 import android.widget.TextView;
37 
38 import com.google.android.gms.common.ConnectionResult;
39 import com.google.android.gms.common.api.GoogleApiClient;
40 import com.google.android.gms.wearable.DataMap;
41 import com.google.android.gms.wearable.Wearable;
42 
43 /**
44  * The watch-side config activity for {@link DigitalWatchFaceService}, which allows for setting the
45  * background color.
46  */
47 public class DigitalWatchFaceWearableConfigActivity extends Activity implements
48         WearableListView.ClickListener, WearableListView.OnScrollListener {
49     private static final String TAG = "DigitalWatchFaceConfig";
50 
51     private GoogleApiClient mGoogleApiClient;
52     private TextView mHeader;
53 
54     @Override
onCreate(Bundle savedInstanceState)55     protected void onCreate(Bundle savedInstanceState) {
56         super.onCreate(savedInstanceState);
57         setContentView(R.layout.activity_digital_config);
58 
59         mHeader = (TextView) findViewById(R.id.header);
60         WearableListView listView = (WearableListView) findViewById(R.id.color_picker);
61         BoxInsetLayout content = (BoxInsetLayout) findViewById(R.id.content);
62         // BoxInsetLayout adds padding by default on round devices. Add some on square devices.
63         content.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
64             @Override
65             public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
66                 if (!insets.isRound()) {
67                     v.setPaddingRelative(
68                             (int) getResources().getDimensionPixelSize(R.dimen.content_padding_start),
69                             v.getPaddingTop(),
70                             v.getPaddingEnd(),
71                             v.getPaddingBottom());
72                 }
73                 return v.onApplyWindowInsets(insets);
74             }
75         });
76 
77         listView.setHasFixedSize(true);
78         listView.setClickListener(this);
79         listView.addOnScrollListener(this);
80 
81         String[] colors = getResources().getStringArray(R.array.color_array);
82         listView.setAdapter(new ColorListAdapter(colors));
83 
84         mGoogleApiClient = new GoogleApiClient.Builder(this)
85                 .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
86                     @Override
87                     public void onConnected(Bundle connectionHint) {
88                         if (Log.isLoggable(TAG, Log.DEBUG)) {
89                             Log.d(TAG, "onConnected: " + connectionHint);
90                         }
91                     }
92 
93                     @Override
94                     public void onConnectionSuspended(int cause) {
95                         if (Log.isLoggable(TAG, Log.DEBUG)) {
96                             Log.d(TAG, "onConnectionSuspended: " + cause);
97                         }
98                     }
99                 })
100                 .addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
101                     @Override
102                     public void onConnectionFailed(ConnectionResult result) {
103                         if (Log.isLoggable(TAG, Log.DEBUG)) {
104                             Log.d(TAG, "onConnectionFailed: " + result);
105                         }
106                     }
107                 })
108                 .addApi(Wearable.API)
109                 .build();
110     }
111 
112     @Override
onStart()113     protected void onStart() {
114         super.onStart();
115         mGoogleApiClient.connect();
116     }
117 
118     @Override
onStop()119     protected void onStop() {
120         if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
121             mGoogleApiClient.disconnect();
122         }
123         super.onStop();
124     }
125 
126     @Override // WearableListView.ClickListener
onClick(WearableListView.ViewHolder viewHolder)127     public void onClick(WearableListView.ViewHolder viewHolder) {
128         ColorItemViewHolder colorItemViewHolder = (ColorItemViewHolder) viewHolder;
129         updateConfigDataItem(colorItemViewHolder.mColorItem.getColor());
130         finish();
131     }
132 
133     @Override // WearableListView.ClickListener
onTopEmptyRegionClick()134     public void onTopEmptyRegionClick() {}
135 
136     @Override // WearableListView.OnScrollListener
onScroll(int scroll)137     public void onScroll(int scroll) {}
138 
139     @Override // WearableListView.OnScrollListener
onAbsoluteScrollChange(int scroll)140     public void onAbsoluteScrollChange(int scroll) {
141         float newTranslation = Math.min(-scroll, 0);
142         mHeader.setTranslationY(newTranslation);
143     }
144 
145     @Override // WearableListView.OnScrollListener
onScrollStateChanged(int scrollState)146     public void onScrollStateChanged(int scrollState) {}
147 
148     @Override // WearableListView.OnScrollListener
onCentralPositionChanged(int centralPosition)149     public void onCentralPositionChanged(int centralPosition) {}
150 
updateConfigDataItem(final int backgroundColor)151     private void updateConfigDataItem(final int backgroundColor) {
152         DataMap configKeysToOverwrite = new DataMap();
153         configKeysToOverwrite.putInt(DigitalWatchFaceUtil.KEY_BACKGROUND_COLOR,
154                 backgroundColor);
155         DigitalWatchFaceUtil.overwriteKeysInConfigDataMap(mGoogleApiClient, configKeysToOverwrite);
156     }
157 
158     private class ColorListAdapter extends WearableListView.Adapter {
159         private final String[] mColors;
160 
ColorListAdapter(String[] colors)161         public ColorListAdapter(String[] colors) {
162             mColors = colors;
163         }
164 
165         @Override
onCreateViewHolder(ViewGroup parent, int viewType)166         public ColorItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
167             return new ColorItemViewHolder(new ColorItem(parent.getContext()));
168         }
169 
170         @Override
onBindViewHolder(WearableListView.ViewHolder holder, int position)171         public void onBindViewHolder(WearableListView.ViewHolder holder, int position) {
172             ColorItemViewHolder colorItemViewHolder = (ColorItemViewHolder) holder;
173             String colorName = mColors[position];
174             colorItemViewHolder.mColorItem.setColor(colorName);
175 
176             RecyclerView.LayoutParams layoutParams =
177                     new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
178                             ViewGroup.LayoutParams.WRAP_CONTENT);
179             int colorPickerItemMargin = (int) getResources()
180                     .getDimension(R.dimen.digital_config_color_picker_item_margin);
181             // Add margins to first and last item to make it possible for user to tap on them.
182             if (position == 0) {
183                 layoutParams.setMargins(0, colorPickerItemMargin, 0, 0);
184             } else if (position == mColors.length - 1) {
185                 layoutParams.setMargins(0, 0, 0, colorPickerItemMargin);
186             } else {
187                 layoutParams.setMargins(0, 0, 0, 0);
188             }
189             colorItemViewHolder.itemView.setLayoutParams(layoutParams);
190         }
191 
192         @Override
getItemCount()193         public int getItemCount() {
194             return mColors.length;
195         }
196     }
197 
198     /** The layout of a color item including image and label. */
199     private static class ColorItem extends LinearLayout implements
200             WearableListView.OnCenterProximityListener {
201         /** The duration of the expand/shrink animation. */
202         private static final int ANIMATION_DURATION_MS = 150;
203         /** The ratio for the size of a circle in shrink state. */
204         private static final float SHRINK_CIRCLE_RATIO = .75f;
205 
206         private static final float SHRINK_LABEL_ALPHA = .5f;
207         private static final float EXPAND_LABEL_ALPHA = 1f;
208 
209         private final TextView mLabel;
210         private final CircledImageView mColor;
211 
212         private final float mExpandCircleRadius;
213         private final float mShrinkCircleRadius;
214 
215         private final ObjectAnimator mExpandCircleAnimator;
216         private final ObjectAnimator mExpandLabelAnimator;
217         private final AnimatorSet mExpandAnimator;
218 
219         private final ObjectAnimator mShrinkCircleAnimator;
220         private final ObjectAnimator mShrinkLabelAnimator;
221         private final AnimatorSet mShrinkAnimator;
222 
ColorItem(Context context)223         public ColorItem(Context context) {
224             super(context);
225             View.inflate(context, R.layout.color_picker_item, this);
226 
227             mLabel = (TextView) findViewById(R.id.label);
228             mColor = (CircledImageView) findViewById(R.id.color);
229 
230             mExpandCircleRadius = mColor.getCircleRadius();
231             mShrinkCircleRadius = mExpandCircleRadius * SHRINK_CIRCLE_RATIO;
232 
233             mShrinkCircleAnimator = ObjectAnimator.ofFloat(mColor, "circleRadius",
234                     mExpandCircleRadius, mShrinkCircleRadius);
235             mShrinkLabelAnimator = ObjectAnimator.ofFloat(mLabel, "alpha",
236                     EXPAND_LABEL_ALPHA, SHRINK_LABEL_ALPHA);
237             mShrinkAnimator = new AnimatorSet().setDuration(ANIMATION_DURATION_MS);
238             mShrinkAnimator.playTogether(mShrinkCircleAnimator, mShrinkLabelAnimator);
239 
240             mExpandCircleAnimator = ObjectAnimator.ofFloat(mColor, "circleRadius",
241                     mShrinkCircleRadius, mExpandCircleRadius);
242             mExpandLabelAnimator = ObjectAnimator.ofFloat(mLabel, "alpha",
243                     SHRINK_LABEL_ALPHA, EXPAND_LABEL_ALPHA);
244             mExpandAnimator = new AnimatorSet().setDuration(ANIMATION_DURATION_MS);
245             mExpandAnimator.playTogether(mExpandCircleAnimator, mExpandLabelAnimator);
246         }
247 
248         @Override
onCenterPosition(boolean animate)249         public void onCenterPosition(boolean animate) {
250             if (animate) {
251                 mShrinkAnimator.cancel();
252                 if (!mExpandAnimator.isRunning()) {
253                     mExpandCircleAnimator.setFloatValues(mColor.getCircleRadius(), mExpandCircleRadius);
254                     mExpandLabelAnimator.setFloatValues(mLabel.getAlpha(), EXPAND_LABEL_ALPHA);
255                     mExpandAnimator.start();
256                 }
257             } else {
258                 mExpandAnimator.cancel();
259                 mColor.setCircleRadius(mExpandCircleRadius);
260                 mLabel.setAlpha(EXPAND_LABEL_ALPHA);
261             }
262         }
263 
264         @Override
onNonCenterPosition(boolean animate)265         public void onNonCenterPosition(boolean animate) {
266             if (animate) {
267                 mExpandAnimator.cancel();
268                 if (!mShrinkAnimator.isRunning()) {
269                     mShrinkCircleAnimator.setFloatValues(mColor.getCircleRadius(), mShrinkCircleRadius);
270                     mShrinkLabelAnimator.setFloatValues(mLabel.getAlpha(), SHRINK_LABEL_ALPHA);
271                     mShrinkAnimator.start();
272                 }
273             } else {
274                 mShrinkAnimator.cancel();
275                 mColor.setCircleRadius(mShrinkCircleRadius);
276                 mLabel.setAlpha(SHRINK_LABEL_ALPHA);
277             }
278         }
279 
setColor(String colorName)280         private void setColor(String colorName) {
281             mLabel.setText(colorName);
282             mColor.setCircleColor(Color.parseColor(colorName));
283         }
284 
getColor()285         private int getColor() {
286             return mColor.getDefaultCircleColor();
287         }
288     }
289 
290     private static class ColorItemViewHolder extends WearableListView.ViewHolder {
291         private final ColorItem mColorItem;
292 
ColorItemViewHolder(ColorItem colorItem)293         public ColorItemViewHolder(ColorItem colorItem) {
294             super(colorItem);
295             mColorItem = colorItem;
296         }
297     }
298 }
299