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.graphics.Rect; 22 import android.net.Uri; 23 import android.os.AsyncTask; 24 import android.os.Bundle; 25 import android.support.v4.view.GestureDetectorCompat; 26 import android.support.wearable.view.DismissOverlayView; 27 import android.support.wearable.view.DotsPageIndicator; 28 import android.support.wearable.view.GridViewPager; 29 import android.util.Log; 30 import android.view.GestureDetector; 31 import android.view.MotionEvent; 32 import android.view.View; 33 import android.view.WindowInsets; 34 import android.widget.FrameLayout; 35 import android.widget.ProgressBar; 36 37 import com.example.android.xyztouristattractions.R; 38 import com.example.android.xyztouristattractions.common.Attraction; 39 import com.example.android.xyztouristattractions.common.Constants; 40 import com.example.android.xyztouristattractions.common.Utils; 41 import com.example.android.xyztouristattractions.service.UtilityService; 42 import com.google.android.gms.common.ConnectionResult; 43 import com.google.android.gms.common.api.GoogleApiClient; 44 import com.google.android.gms.maps.model.LatLng; 45 import com.google.android.gms.wearable.DataApi; 46 import com.google.android.gms.wearable.DataMap; 47 import com.google.android.gms.wearable.DataMapItem; 48 import com.google.android.gms.wearable.Wearable; 49 50 import java.util.ArrayList; 51 import java.util.Iterator; 52 import java.util.List; 53 import java.util.concurrent.TimeUnit; 54 55 /** 56 * The main Wear activity that displays nearby attractions in a 57 * {@link android.support.wearable.view.GridViewPager}. Each row shows 58 * one attraction and each column shows information or actions for that 59 * particular attraction. 60 */ 61 public class AttractionsActivity extends Activity 62 implements AttractionsGridPagerAdapter.OnChromeFadeListener { 63 private static final String TAG = AttractionsActivity.class.getSimpleName(); 64 65 private GestureDetectorCompat mGestureDetector; 66 private DismissOverlayView mDismissOverlayView; 67 private GridViewPager mGridViewPager; 68 private AttractionsGridPagerAdapter mAdapter; 69 private DotsPageIndicator mDotsPageIndicator; 70 private ProgressBar mProgressBar; 71 private Rect mInsets = new Rect(0, 0, 0, 0); 72 73 private ArrayList<Attraction> mAttractions = new ArrayList<Attraction>(); 74 75 @Override onCreate(Bundle savedInstanceState)76 protected void onCreate(Bundle savedInstanceState) { 77 super.onCreate(savedInstanceState); 78 79 setContentView(R.layout.activity_main); 80 final FrameLayout topFrameLayout = (FrameLayout) findViewById(R.id.topFrameLayout); 81 mProgressBar = (ProgressBar) findViewById(R.id.progressBar); 82 mGridViewPager = (GridViewPager) findViewById(R.id.gridViewPager); 83 mDotsPageIndicator = (DotsPageIndicator) findViewById(R.id.dotsPageIndicator); 84 mAdapter = new AttractionsGridPagerAdapter(this, mAttractions); 85 mAdapter.setOnChromeFadeListener(this); 86 mGridViewPager.setAdapter(mAdapter); 87 mDotsPageIndicator.setPager(mGridViewPager); 88 mDotsPageIndicator.setOnPageChangeListener(mAdapter); 89 90 topFrameLayout.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() { 91 @Override 92 public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { 93 // Call through to super implementation 94 insets = topFrameLayout.onApplyWindowInsets(insets); 95 96 boolean round = insets.isRound(); 97 98 // Store system window insets regardless of screen shape 99 mInsets.set(insets.getSystemWindowInsetLeft(), 100 insets.getSystemWindowInsetTop(), 101 insets.getSystemWindowInsetRight(), 102 insets.getSystemWindowInsetBottom()); 103 104 if (round) { 105 // On a round screen calculate the square inset to use. 106 // Alternatively could use BoxInsetLayout, although calculating 107 // the inset ourselves lets us position views outside the center 108 // box. For example, slightly lower on the round screen (by giving 109 // up some horizontal space). 110 mInsets = Utils.calculateBottomInsetsOnRoundDevice( 111 getWindowManager().getDefaultDisplay(), mInsets); 112 113 // Boost the dots indicator up by the bottom inset 114 FrameLayout.LayoutParams params = 115 (FrameLayout.LayoutParams) mDotsPageIndicator.getLayoutParams(); 116 params.bottomMargin = mInsets.bottom; 117 mDotsPageIndicator.setLayoutParams(params); 118 } 119 120 mAdapter.setInsets(mInsets); 121 return insets; 122 } 123 }); 124 125 // Set up the DismissOverlayView 126 mDismissOverlayView = (DismissOverlayView) findViewById(R.id.dismiss_overlay); 127 mDismissOverlayView.setIntroText(getString(R.string.exit_intro_text)); 128 mDismissOverlayView.showIntroIfNecessary(); 129 mGestureDetector = new GestureDetectorCompat(this, new LongPressListener()); 130 131 Uri attractionsUri = getIntent().getParcelableExtra(Constants.EXTRA_ATTRACTIONS_URI); 132 if (attractionsUri != null) { 133 new FetchDataAsyncTask(this).execute(attractionsUri); 134 UtilityService.clearNotification(this); 135 UtilityService.clearRemoteNotifications(this); 136 } else { 137 finish(); 138 } 139 } 140 141 @Override dispatchTouchEvent(MotionEvent event)142 public boolean dispatchTouchEvent(MotionEvent event) { 143 return mGestureDetector.onTouchEvent(event) || super.dispatchTouchEvent(event); 144 } 145 146 @Override onTouchEvent(MotionEvent event)147 public boolean onTouchEvent(MotionEvent event) { 148 return mGestureDetector.onTouchEvent(event) || super.onTouchEvent(event); 149 } 150 151 @Override onChromeFadeIn()152 public void onChromeFadeIn() { 153 // As the custom UI chrome fades in, also fade the DotsPageIndicator in 154 mDotsPageIndicator.animate().alpha(1).setDuration( 155 AttractionsGridPagerAdapter.FADE_IN_TIME_MS).start(); 156 } 157 158 @Override onChromeFadeOut()159 public void onChromeFadeOut() { 160 // As the custom UI chrome fades out, also fade the DotsPageIndicator out 161 mDotsPageIndicator.animate().alpha(0).setDuration( 162 AttractionsGridPagerAdapter.FADE_OUT_TIME_MS).start(); 163 } 164 165 private class LongPressListener extends GestureDetector.SimpleOnGestureListener { 166 @Override onLongPress(MotionEvent event)167 public void onLongPress(MotionEvent event) { 168 mDismissOverlayView.show(); 169 } 170 } 171 172 /** 173 * A background task to load the attraction data via the Wear DataApi. 174 * This can take a second or two sometimes as several images need to 175 * be loaded. 176 */ 177 private class FetchDataAsyncTask extends 178 AsyncTask<Uri, Void, ArrayList<Attraction>> { 179 180 private Context mContext; 181 FetchDataAsyncTask(Context context)182 public FetchDataAsyncTask(Context context) { 183 mContext = context; 184 } 185 186 @Override doInBackground(Uri... params)187 protected ArrayList<Attraction> doInBackground(Uri... params) { 188 mAttractions.clear(); 189 190 // Connect to Play Services and the Wearable API 191 GoogleApiClient googleApiClient = new GoogleApiClient.Builder(mContext) 192 .addApi(Wearable.API) 193 .build(); 194 195 ConnectionResult connectionResult = googleApiClient.blockingConnect( 196 Constants.GOOGLE_API_CLIENT_TIMEOUT_S, TimeUnit.SECONDS); 197 198 if (!connectionResult.isSuccess() || !googleApiClient.isConnected()) { 199 Log.e(TAG, String.format(Constants.GOOGLE_API_CLIENT_ERROR_MSG, 200 connectionResult.getErrorCode())); 201 return null; 202 } 203 204 Uri attractionsUri = params[0]; 205 DataApi.DataItemResult dataItemResult = 206 Wearable.DataApi.getDataItem(googleApiClient, attractionsUri).await(); 207 208 if (dataItemResult.getStatus().isSuccess() && dataItemResult.getDataItem() != null) { 209 DataMapItem dataMapItem = DataMapItem.fromDataItem(dataItemResult.getDataItem()); 210 List<DataMap> attractionsData = 211 dataMapItem.getDataMap().getDataMapArrayList(Constants.EXTRA_ATTRACTIONS); 212 213 // Loop through each attraction, adding them to the list 214 Iterator<DataMap> itr = attractionsData.iterator(); 215 while (itr.hasNext()) { 216 DataMap attractionData = itr.next(); 217 218 Attraction attraction = new Attraction(); 219 attraction.name = attractionData.getString(Constants.EXTRA_TITLE); 220 attraction.description = 221 attractionData.getString(Constants.EXTRA_DESCRIPTION); 222 attraction.city = attractionData.get(Constants.EXTRA_CITY); 223 attraction.distance = 224 attractionData.getString(Constants.EXTRA_DISTANCE); 225 attraction.location = new LatLng( 226 attractionData.getDouble(Constants.EXTRA_LOCATION_LAT), 227 attractionData.getDouble(Constants.EXTRA_LOCATION_LNG)); 228 attraction.image = Utils.loadBitmapFromAsset(googleApiClient, 229 attractionData.getAsset(Constants.EXTRA_IMAGE)); 230 attraction.secondaryImage = Utils.loadBitmapFromAsset(googleApiClient, 231 attractionData.getAsset(Constants.EXTRA_IMAGE_SECONDARY)); 232 233 mAttractions.add(attraction); 234 } 235 } 236 237 googleApiClient.disconnect(); 238 239 return mAttractions; 240 } 241 242 @Override onPostExecute(ArrayList<Attraction> result)243 protected void onPostExecute(ArrayList<Attraction> result) { 244 if (result != null && result.size() > 0) { 245 // Update UI based on the result of the background processing 246 mAdapter.setData(result); 247 mAdapter.notifyDataSetChanged(); 248 mProgressBar.setVisibility(View.GONE); 249 mDotsPageIndicator.setVisibility(View.VISIBLE); 250 mGridViewPager.setVisibility(View.VISIBLE); 251 } else { 252 finish(); 253 } 254 } 255 } 256 } 257