1 /* 2 * Copyright (C) 2016 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.wearverifyremoteapp; 17 18 import android.content.Intent; 19 import android.net.Uri; 20 import android.os.Bundle; 21 import android.os.Handler; 22 import android.os.ResultReceiver; 23 import android.support.annotation.NonNull; 24 import android.support.annotation.Nullable; 25 import android.support.wearable.activity.WearableActivity; 26 import android.support.wearable.view.ConfirmationOverlay; 27 import android.util.Log; 28 import android.view.View; 29 import android.widget.Button; 30 import android.widget.TextView; 31 32 import com.google.android.gms.common.ConnectionResult; 33 import com.google.android.gms.common.api.GoogleApiClient; 34 import com.google.android.gms.common.api.PendingResult; 35 import com.google.android.gms.common.api.ResultCallback; 36 import com.google.android.gms.wearable.CapabilityApi; 37 import com.google.android.gms.wearable.CapabilityInfo; 38 import com.google.android.gms.wearable.Node; 39 import com.google.android.gms.wearable.Wearable; 40 import com.google.android.wearable.intent.RemoteIntent; 41 import com.google.android.wearable.playstore.PlayStoreAvailability; 42 43 import java.util.Set; 44 45 /** 46 * Checks if the phone app is installed on remote device. If it is not, allows user to open app 47 * listing on the phone's Play or App Store. 48 */ 49 public class MainWearActivity extends WearableActivity implements 50 GoogleApiClient.ConnectionCallbacks, 51 GoogleApiClient.OnConnectionFailedListener, 52 CapabilityApi.CapabilityListener { 53 54 private static final String TAG = "MainWearActivity"; 55 56 private static final String WELCOME_MESSAGE = "Welcome to our Wear app!\n\n"; 57 58 private static final String CHECKING_MESSAGE = 59 WELCOME_MESSAGE + "Checking for Mobile app...\n"; 60 61 private static final String MISSING_MESSAGE = 62 WELCOME_MESSAGE 63 + "You are missing the required phone app, please click on the button below to " 64 + "install it on your phone.\n"; 65 66 private static final String INSTALLED_MESSAGE = 67 WELCOME_MESSAGE 68 + "Mobile app installed on your %s!\n\nYou can now use MessageApi, " 69 + "DataApi, etc."; 70 71 // Name of capability listed in Phone app's wear.xml. 72 // IMPORTANT NOTE: This should be named differently than your Wear app's capability. 73 private static final String CAPABILITY_PHONE_APP = "verify_remote_example_phone_app"; 74 75 // Links to install mobile app for both Android (Play Store) and iOS. 76 // TODO: Replace with your links/packages. 77 private static final String PLAY_STORE_APP_URI = 78 "market://details?id=com.example.android.wearable.wear.wearverifyremoteapp"; 79 80 // TODO: Replace with your links/packages. 81 private static final String APP_STORE_APP_URI = 82 "https://itunes.apple.com/us/app/android-wear/id986496028?mt=8"; 83 84 // Result from sending RemoteIntent to phone to open app in play/app store. 85 private final ResultReceiver mResultReceiver = new ResultReceiver(new Handler()) { 86 @Override 87 protected void onReceiveResult(int resultCode, Bundle resultData) { 88 89 if (resultCode == RemoteIntent.RESULT_OK) { 90 new ConfirmationOverlay().showOn(MainWearActivity.this); 91 92 } else if (resultCode == RemoteIntent.RESULT_FAILED) { 93 new ConfirmationOverlay() 94 .setType(ConfirmationOverlay.FAILURE_ANIMATION) 95 .showOn(MainWearActivity.this); 96 97 } else { 98 throw new IllegalStateException("Unexpected result " + resultCode); 99 } 100 } 101 }; 102 103 private TextView mInformationTextView; 104 private Button mRemoteOpenButton; 105 106 private Node mAndroidPhoneNodeWithApp; 107 108 private GoogleApiClient mGoogleApiClient; 109 110 @Override onCreate(Bundle savedInstanceState)111 protected void onCreate(Bundle savedInstanceState) { 112 Log.d(TAG, "onCreate()"); 113 super.onCreate(savedInstanceState); 114 115 setContentView(R.layout.activity_main); 116 setAmbientEnabled(); 117 118 mInformationTextView = (TextView) findViewById(R.id.information_text_view); 119 mRemoteOpenButton = (Button) findViewById(R.id.remote_open_button); 120 121 mInformationTextView.setText(CHECKING_MESSAGE); 122 123 mRemoteOpenButton.setOnClickListener(new View.OnClickListener() { 124 @Override 125 public void onClick(View view) { 126 openAppInStoreOnPhone(); 127 } 128 }); 129 130 mGoogleApiClient = new GoogleApiClient.Builder(this) 131 .addApi(Wearable.API) 132 .addConnectionCallbacks(this) 133 .addOnConnectionFailedListener(this) 134 .build(); 135 } 136 137 138 @Override onPause()139 protected void onPause() { 140 Log.d(TAG, "onPause()"); 141 super.onPause(); 142 143 if ((mGoogleApiClient != null) && mGoogleApiClient.isConnected()) { 144 Wearable.CapabilityApi.removeCapabilityListener( 145 mGoogleApiClient, 146 this, 147 CAPABILITY_PHONE_APP); 148 149 mGoogleApiClient.disconnect(); 150 } 151 } 152 153 @Override onResume()154 protected void onResume() { 155 Log.d(TAG, "onResume()"); 156 super.onResume(); 157 if (mGoogleApiClient != null) { 158 mGoogleApiClient.connect(); 159 } 160 } 161 162 @Override onConnected(@ullable Bundle bundle)163 public void onConnected(@Nullable Bundle bundle) { 164 Log.d(TAG, "onConnected()"); 165 166 // Set up listeners for capability changes (install/uninstall of remote app). 167 Wearable.CapabilityApi.addCapabilityListener( 168 mGoogleApiClient, 169 this, 170 CAPABILITY_PHONE_APP); 171 172 checkIfPhoneHasApp(); 173 } 174 175 @Override onConnectionSuspended(int i)176 public void onConnectionSuspended(int i) { 177 Log.d(TAG, "onConnectionSuspended(): connection to location client suspended: " + i); 178 } 179 180 @Override onConnectionFailed(@onNull ConnectionResult connectionResult)181 public void onConnectionFailed(@NonNull ConnectionResult connectionResult) { 182 Log.e(TAG, "onConnectionFailed(): " + connectionResult); 183 } 184 185 /* 186 * Updates UI when capabilities change (install/uninstall phone app). 187 */ onCapabilityChanged(CapabilityInfo capabilityInfo)188 public void onCapabilityChanged(CapabilityInfo capabilityInfo) { 189 Log.d(TAG, "onCapabilityChanged(): " + capabilityInfo); 190 191 mAndroidPhoneNodeWithApp = pickBestNodeId(capabilityInfo.getNodes()); 192 verifyNodeAndUpdateUI(); 193 } 194 checkIfPhoneHasApp()195 private void checkIfPhoneHasApp() { 196 Log.d(TAG, "checkIfPhoneHasApp()"); 197 198 PendingResult<CapabilityApi.GetCapabilityResult> pendingResult = 199 Wearable.CapabilityApi.getCapability( 200 mGoogleApiClient, 201 CAPABILITY_PHONE_APP, 202 CapabilityApi.FILTER_ALL); 203 204 pendingResult.setResultCallback(new ResultCallback<CapabilityApi.GetCapabilityResult>() { 205 206 @Override 207 public void onResult(@NonNull CapabilityApi.GetCapabilityResult getCapabilityResult) { 208 Log.d(TAG, "onResult(): " + getCapabilityResult); 209 210 if (getCapabilityResult.getStatus().isSuccess()) { 211 CapabilityInfo capabilityInfo = getCapabilityResult.getCapability(); 212 mAndroidPhoneNodeWithApp = pickBestNodeId(capabilityInfo.getNodes()); 213 verifyNodeAndUpdateUI(); 214 215 } else { 216 Log.d(TAG, "Failed CapabilityApi: " + getCapabilityResult.getStatus()); 217 } 218 } 219 }); 220 } 221 verifyNodeAndUpdateUI()222 private void verifyNodeAndUpdateUI() { 223 224 if (mAndroidPhoneNodeWithApp != null) { 225 226 // TODO: Add your code to communicate with the phone app via 227 // Wear APIs (MessageApi, DataApi, etc.) 228 229 String installMessage = 230 String.format(INSTALLED_MESSAGE, mAndroidPhoneNodeWithApp.getDisplayName()); 231 Log.d(TAG, installMessage); 232 mInformationTextView.setText(installMessage); 233 mRemoteOpenButton.setVisibility(View.INVISIBLE); 234 235 } else { 236 Log.d(TAG, MISSING_MESSAGE); 237 mInformationTextView.setText(MISSING_MESSAGE); 238 mRemoteOpenButton.setVisibility(View.VISIBLE); 239 } 240 } 241 openAppInStoreOnPhone()242 private void openAppInStoreOnPhone() { 243 Log.d(TAG, "openAppInStoreOnPhone()"); 244 245 int playStoreAvailabilityOnPhone = 246 PlayStoreAvailability.getPlayStoreAvailabilityOnPhone(getApplicationContext()); 247 248 switch (playStoreAvailabilityOnPhone) { 249 250 // Android phone with the Play Store. 251 case PlayStoreAvailability.PLAY_STORE_ON_PHONE_AVAILABLE: 252 Log.d(TAG, "\tPLAY_STORE_ON_PHONE_AVAILABLE"); 253 254 // Create Remote Intent to open Play Store listing of app on remote device. 255 Intent intentAndroid = 256 new Intent(Intent.ACTION_VIEW) 257 .addCategory(Intent.CATEGORY_BROWSABLE) 258 .setData(Uri.parse(PLAY_STORE_APP_URI)); 259 260 RemoteIntent.startRemoteActivity( 261 getApplicationContext(), 262 intentAndroid, 263 mResultReceiver); 264 break; 265 266 // Assume iPhone (iOS device) or Android without Play Store (not supported right now). 267 case PlayStoreAvailability.PLAY_STORE_ON_PHONE_UNAVAILABLE: 268 Log.d(TAG, "\tPLAY_STORE_ON_PHONE_UNAVAILABLE"); 269 270 // Create Remote Intent to open App Store listing of app on iPhone. 271 Intent intentIOS = 272 new Intent(Intent.ACTION_VIEW) 273 .addCategory(Intent.CATEGORY_BROWSABLE) 274 .setData(Uri.parse(APP_STORE_APP_URI)); 275 276 RemoteIntent.startRemoteActivity( 277 getApplicationContext(), 278 intentIOS, 279 mResultReceiver); 280 break; 281 282 case PlayStoreAvailability.PLAY_STORE_ON_PHONE_ERROR_UNKNOWN: 283 Log.d(TAG, "\tPLAY_STORE_ON_PHONE_ERROR_UNKNOWN"); 284 break; 285 } 286 } 287 288 /* 289 * There should only ever be one phone in a node set (much less w/ the correct capability), so 290 * I am just grabbing the first one (which should be the only one). 291 */ pickBestNodeId(Set<Node> nodes)292 private Node pickBestNodeId(Set<Node> nodes) { 293 Log.d(TAG, "pickBestNodeId(): " + nodes); 294 295 Node bestNodeId = null; 296 // Find a nearby node/phone or pick one arbitrarily. Realistically, there is only one phone. 297 for (Node node : nodes) { 298 bestNodeId = node; 299 } 300 return bestNodeId; 301 } 302 }