1 /* 2 * Copyright 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 17 package com.example.android.xyztouristattractions.ui; 18 19 import android.app.Activity; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.graphics.Rect; 23 import android.graphics.drawable.BitmapDrawable; 24 import android.graphics.drawable.ColorDrawable; 25 import android.graphics.drawable.Drawable; 26 import android.os.Handler; 27 import android.support.wearable.activity.ConfirmationActivity; 28 import android.support.wearable.view.ActionPage; 29 import android.support.wearable.view.CardFrame; 30 import android.support.wearable.view.CardScrollView; 31 import android.support.wearable.view.GridPagerAdapter; 32 import android.support.wearable.view.GridViewPager; 33 import android.text.TextUtils; 34 import android.view.Gravity; 35 import android.view.LayoutInflater; 36 import android.view.View; 37 import android.view.ViewGroup; 38 import android.widget.FrameLayout; 39 import android.widget.ImageView; 40 import android.widget.TextView; 41 42 import com.example.android.xyztouristattractions.R; 43 import com.example.android.xyztouristattractions.common.Attraction; 44 import com.example.android.xyztouristattractions.common.Constants; 45 import com.example.android.xyztouristattractions.service.UtilityService; 46 47 import java.util.ArrayList; 48 49 /** 50 * This adapter backs the main GridViewPager component found in 51 * {@link com.example.android.xyztouristattractions.ui.AttractionsActivity}. 52 */ 53 public class AttractionsGridPagerAdapter extends GridPagerAdapter 54 implements GridViewPager.OnPageChangeListener { 55 56 public static final int FADE_IN_TIME_MS = 250; 57 public static final int FADE_OUT_TIME_MS = 500; 58 private static final int GRID_COLUMN_COUNT = 5; 59 private static final int FADE_OUT_DELAY_MS = 1500; 60 private static final int PAGER_PRIMARY_IMAGE_COLUMN = 0; 61 private static final int PAGER_SECONDARY_IMAGE_COLUMN = 1; 62 private static final int PAGER_DESCRIPTION_COLUMN = 2; 63 private static final int PAGER_NAVIGATE_ACTION_COLUMN = 3; 64 private static final int PAGER_OPEN_ACTION_COLUMN = 4; 65 66 private Context mContext; 67 private LayoutInflater mLayoutInflater; 68 private ArrayList<Attraction> mAttractions; 69 private Rect mInsets = new Rect(); 70 private DelayedHide mDelayedHide = new DelayedHide(); 71 private OnChromeFadeListener mOnChromeFadeListener; 72 AttractionsGridPagerAdapter( Context context, ArrayList<Attraction> attractions)73 public AttractionsGridPagerAdapter( 74 Context context, ArrayList<Attraction> attractions) { 75 super(); 76 mContext = context; 77 mLayoutInflater = LayoutInflater.from(context); 78 mAttractions = attractions; 79 } 80 setData(ArrayList<Attraction> attractions)81 public void setData(ArrayList<Attraction> attractions) { 82 mAttractions = attractions; 83 } 84 setInsets(Rect insets)85 public void setInsets(Rect insets) { 86 mInsets = insets; 87 } 88 89 @Override getRowCount()90 public int getRowCount() { 91 return (mAttractions != null && mAttractions.size() > 0) ? mAttractions.size() : 1; 92 } 93 94 @Override getColumnCount(int i)95 public int getColumnCount(int i) { 96 return GRID_COLUMN_COUNT; 97 } 98 99 @Override instantiateItem(ViewGroup container, int row, final int column)100 public Object instantiateItem(ViewGroup container, int row, final int column) { 101 if (mAttractions != null && mAttractions.size() > 0) { 102 final Attraction attraction = mAttractions.get(row); 103 switch (column) { 104 case PAGER_PRIMARY_IMAGE_COLUMN: 105 case PAGER_SECONDARY_IMAGE_COLUMN: 106 // Two pages of full screen images, one with the attraction name 107 // and one with the distance to the attraction 108 final View view = mLayoutInflater.inflate( 109 R.layout.gridpager_fullscreen_image, container, false); 110 ImageView imageView = (ImageView) view.findViewById(R.id.imageView); 111 TextView textView = (TextView) view.findViewById(R.id.textView); 112 FrameLayout overlayTextLayout = 113 (FrameLayout) view.findViewById(R.id.overlaytext); 114 115 mDelayedHide.add(overlayTextLayout); 116 view.setOnClickListener(mDelayedHide); 117 118 FrameLayout.LayoutParams params = 119 (FrameLayout.LayoutParams) textView.getLayoutParams(); 120 params.bottomMargin = params.bottomMargin + mInsets.bottom; 121 params.leftMargin = mInsets.left; 122 params.rightMargin = mInsets.right; 123 textView.setLayoutParams(params); 124 125 if (column == PAGER_PRIMARY_IMAGE_COLUMN) { 126 imageView.setImageBitmap(attraction.image); 127 textView.setText(attraction.name); 128 } else { 129 imageView.setImageBitmap(attraction.secondaryImage); 130 if (TextUtils.isEmpty(attraction.distance)) { 131 overlayTextLayout.setVisibility(View.GONE); 132 } else { 133 textView.setText(mContext.getString( 134 R.string.map_caption, attraction.distance)); 135 } 136 } 137 container.addView(view); 138 return view; 139 case PAGER_DESCRIPTION_COLUMN: 140 // The description card page 141 CardScrollView cardScrollView = (CardScrollView) mLayoutInflater.inflate( 142 R.layout.gridpager_card, container, false); 143 TextView descTextView = (TextView) cardScrollView.findViewById(R.id.textView); 144 descTextView.setText(attraction.description); 145 cardScrollView.setCardGravity(Gravity.BOTTOM); 146 cardScrollView.setExpansionEnabled(true); 147 cardScrollView.setExpansionDirection(CardFrame.EXPAND_DOWN); 148 cardScrollView.setExpansionFactor(10); 149 container.addView(cardScrollView); 150 return cardScrollView; 151 case PAGER_NAVIGATE_ACTION_COLUMN: 152 // The navigate action 153 final ActionPage navActionPage = (ActionPage) mLayoutInflater.inflate( 154 R.layout.gridpager_action, container, false); 155 156 navActionPage.setOnClickListener(getStartActionClickListener( 157 attraction, Constants.START_NAVIGATION_PATH, 158 ConfirmationActivity.SUCCESS_ANIMATION)); 159 navActionPage.setImageResource(R.drawable.ic_full_directions_walking); 160 navActionPage.setText(mContext.getString(R.string.action_navigate)); 161 162 container.addView(navActionPage); 163 return navActionPage; 164 case PAGER_OPEN_ACTION_COLUMN: 165 // The "open on device" action 166 final ActionPage openActionPage = (ActionPage) mLayoutInflater.inflate( 167 R.layout.gridpager_action, container, false); 168 169 openActionPage.setOnClickListener(getStartActionClickListener( 170 attraction, Constants.START_ATTRACTION_PATH, 171 ConfirmationActivity.OPEN_ON_PHONE_ANIMATION)); 172 openActionPage.setImageResource(R.drawable.ic_full_openonphone); 173 openActionPage.setText(mContext.getString(R.string.action_open)); 174 175 container.addView(openActionPage); 176 return openActionPage; 177 } 178 } 179 return new View(mContext); 180 } 181 182 @Override getBackgroundForPage(int row, int column)183 public Drawable getBackgroundForPage(int row, int column) { 184 if (column == 0) { 185 return new ColorDrawable(0); // Empty black drawable 186 } 187 if (mAttractions.size() > 0 && mAttractions.get(row).image != null) { 188 return new BitmapDrawable(mContext.getResources(), mAttractions.get(row).image); 189 } 190 return super.getBackgroundForPage(row, column); 191 } 192 193 @Override destroyItem(ViewGroup viewGroup, int row, int column, Object object)194 public void destroyItem(ViewGroup viewGroup, int row, int column, Object object) { 195 mDelayedHide.remove((View) object); 196 viewGroup.removeView((View)object); 197 } 198 199 @Override isViewFromObject(View view, Object object)200 public boolean isViewFromObject(View view, Object object) { 201 return view == object; 202 } 203 204 @Override onPageScrolled(int posX, int posY, float posOffsetX, float posOffsetY, int posOffsetPixelsX, int posOffsetPixelsY)205 public void onPageScrolled(int posX, int posY, float posOffsetX, float posOffsetY, 206 int posOffsetPixelsX, int posOffsetPixelsY) {} 207 208 @Override onPageSelected(int row, int col)209 public void onPageSelected(int row, int col) {} 210 211 @Override onPageScrollStateChanged(int state)212 public void onPageScrollStateChanged(int state) { 213 mDelayedHide.show(); 214 } 215 216 /** 217 * Use the Wear Message API to execute an action. Clears local and remote notifications and 218 * also runs a confirmation animation before finishing the Wear activity. 219 * 220 * @param attraction The attraction to start the action on 221 * @param pathName The Wear Message API pathname 222 * @param confirmAnimationType The confirmation animation type from ConfirmationActivity 223 */ startAction(Attraction attraction, String pathName, int confirmAnimationType)224 private void startAction(Attraction attraction, String pathName, int confirmAnimationType) { 225 Intent intent = new Intent(mContext, ConfirmationActivity.class); 226 intent.putExtra(ConfirmationActivity.EXTRA_ANIMATION_TYPE, confirmAnimationType); 227 intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); 228 mContext.startActivity(intent); 229 230 UtilityService.clearNotification(mContext); 231 UtilityService.clearRemoteNotifications(mContext); 232 UtilityService.startDeviceActivity(mContext, pathName, attraction.name, attraction.city); 233 234 ((Activity)mContext).finish(); 235 } 236 237 /** 238 * Helper method to generate the OnClickListener for the attraction actions. 239 */ getStartActionClickListener(final Attraction attraction, final String pathName, final int confirmAnimationType)240 private View.OnClickListener getStartActionClickListener(final Attraction attraction, 241 final String pathName, final int confirmAnimationType) { 242 View.OnClickListener clickListener = new View.OnClickListener() { 243 @Override 244 public void onClick(View v) { 245 startAction(attraction, pathName, confirmAnimationType); 246 } 247 }; 248 return clickListener; 249 } 250 setOnChromeFadeListener(OnChromeFadeListener listener)251 public void setOnChromeFadeListener(OnChromeFadeListener listener) { 252 mOnChromeFadeListener = listener; 253 } 254 255 public interface OnChromeFadeListener { onChromeFadeIn()256 void onChromeFadeIn(); onChromeFadeOut()257 void onChromeFadeOut(); 258 } 259 260 /** 261 * Helper class to fade out views based on a delay and fade them back in if needed as well. 262 */ 263 private class DelayedHide implements View.OnClickListener { 264 265 ArrayList<View> hideViews = new ArrayList<View>(GRID_COLUMN_COUNT); 266 Handler mHideHandler = new Handler(); 267 boolean mIsHidden = false; 268 269 Runnable mHideRunnable = new Runnable() { 270 @Override 271 public void run() { 272 hide(); 273 } 274 }; 275 add(View newView)276 void add(View newView) { 277 hideViews.add(newView); 278 delayedHide(); 279 } 280 remove(View removeView)281 void remove(View removeView) { 282 hideViews.remove(removeView); 283 } 284 show()285 void show() { 286 mIsHidden = false; 287 if (mOnChromeFadeListener != null) { 288 mOnChromeFadeListener.onChromeFadeIn(); 289 } 290 for (View view : hideViews) { 291 if (view != null) { 292 view.animate().alpha(1).setDuration(FADE_IN_TIME_MS).start(); 293 } 294 } 295 delayedHide(); 296 } 297 hide()298 void hide() { 299 mIsHidden = true; 300 mHideHandler.removeCallbacks(mHideRunnable); 301 if (mOnChromeFadeListener != null) { 302 mOnChromeFadeListener.onChromeFadeOut(); 303 } 304 for (int i=0; i<hideViews.size(); i++) { 305 if (hideViews.get(i) != null) { 306 hideViews.get(i).animate().alpha(0).setDuration(FADE_OUT_TIME_MS).start(); 307 } 308 } 309 } 310 delayedHide()311 void delayedHide() { 312 mHideHandler.removeCallbacks(mHideRunnable); 313 mHideHandler.postDelayed(mHideRunnable, FADE_OUT_DELAY_MS); 314 } 315 316 @Override onClick(View v)317 public void onClick(View v) { 318 if (mIsHidden) { 319 show(); 320 } else { 321 hide(); 322 } 323 } 324 } 325 } 326