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.v7.app.AppCompatActivity; 26 import android.util.Log; 27 import android.view.View; 28 import android.widget.Button; 29 import android.widget.TextView; 30 import android.widget.Toast; 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.NodeApi; 40 import com.google.android.gms.wearable.Wearable; 41 import com.google.android.wearable.intent.RemoteIntent; 42 43 import java.util.ArrayList; 44 import java.util.List; 45 import java.util.Set; 46 47 /** 48 * Checks if the sample's Wear app is installed on remote Wear device(s). If it is not, allows the 49 * user to open the app listing on the Wear devices' Play Store. 50 */ 51 public class MainMobileActivity extends AppCompatActivity implements 52 GoogleApiClient.ConnectionCallbacks, 53 GoogleApiClient.OnConnectionFailedListener, 54 CapabilityApi.CapabilityListener { 55 56 private static final String TAG = "MainMobileActivity"; 57 58 private static final String WELCOME_MESSAGE = "Welcome to our Mobile app!\n\n"; 59 60 private static final String CHECKING_MESSAGE = 61 WELCOME_MESSAGE + "Checking for Wear Devices for app...\n"; 62 63 private static final String NO_DEVICES = 64 WELCOME_MESSAGE 65 + "You have no Wear devices linked to your phone at this time.\n"; 66 67 private static final String MISSING_ALL_MESSAGE = 68 WELCOME_MESSAGE 69 + "You are missing the Wear app on all your Wear Devices, please click on the " 70 + "button below to install it on those device(s).\n"; 71 72 private static final String INSTALLED_SOME_DEVICES_MESSAGE = 73 WELCOME_MESSAGE 74 + "Wear app installed on some your device(s) (%s)!\n\nYou can now use the " 75 + "MessageApi, DataApi, etc.\n\n" 76 + "To install the Wear app on the other devices, please click on the button " 77 + "below.\n"; 78 79 private static final String INSTALLED_ALL_DEVICES_MESSAGE = 80 WELCOME_MESSAGE 81 + "Wear app installed on all your devices (%s)!\n\nYou can now use the " 82 + "MessageApi, DataApi, etc."; 83 84 // Name of capability listed in Wear app's wear.xml. 85 // IMPORTANT NOTE: This should be named differently than your Phone app's capability. 86 private static final String CAPABILITY_WEAR_APP = "verify_remote_example_wear_app"; 87 88 // Links to Wear app (Play Store). 89 // TODO: Replace with your links/packages. 90 private static final String PLAY_STORE_APP_URI = 91 "market://details?id=com.example.android.wearable.wear.wearverifyremoteapp"; 92 93 // Result from sending RemoteIntent to wear device(s) to open app in play/app store. 94 private final ResultReceiver mResultReceiver = new ResultReceiver(new Handler()) { 95 @Override 96 protected void onReceiveResult(int resultCode, Bundle resultData) { 97 Log.d(TAG, "onReceiveResult: " + resultCode); 98 99 if (resultCode == RemoteIntent.RESULT_OK) { 100 Toast toast = Toast.makeText( 101 getApplicationContext(), 102 "Play Store Request to Wear device successful.", 103 Toast.LENGTH_SHORT); 104 toast.show(); 105 106 } else if (resultCode == RemoteIntent.RESULT_FAILED) { 107 Toast toast = Toast.makeText( 108 getApplicationContext(), 109 "Play Store Request Failed. Wear device(s) may not support Play Store, " 110 + " that is, the Wear device may be version 1.0.", 111 Toast.LENGTH_LONG); 112 toast.show(); 113 114 } else { 115 throw new IllegalStateException("Unexpected result " + resultCode); 116 } 117 } 118 }; 119 120 private TextView mInformationTextView; 121 private Button mRemoteOpenButton; 122 123 private Set<Node> mWearNodesWithApp; 124 private List<Node> mAllConnectedNodes; 125 126 private GoogleApiClient mGoogleApiClient; 127 128 @Override onCreate(Bundle savedInstanceState)129 protected void onCreate(Bundle savedInstanceState) { 130 Log.d(TAG, "onCreate()"); 131 super.onCreate(savedInstanceState); 132 setContentView(R.layout.activity_main); 133 134 mInformationTextView = (TextView) findViewById(R.id.information_text_view); 135 mRemoteOpenButton = (Button) findViewById(R.id.remote_open_button); 136 137 mInformationTextView.setText(CHECKING_MESSAGE); 138 139 mRemoteOpenButton.setOnClickListener(new View.OnClickListener() { 140 @Override 141 public void onClick(View v) { 142 openPlayStoreOnWearDevicesWithoutApp(); 143 } 144 }); 145 146 mGoogleApiClient = new GoogleApiClient.Builder(this) 147 .addApi(Wearable.API) 148 .addConnectionCallbacks(this) 149 .addOnConnectionFailedListener(this) 150 .build(); 151 } 152 153 154 @Override onPause()155 protected void onPause() { 156 Log.d(TAG, "onPause()"); 157 super.onPause(); 158 159 if ((mGoogleApiClient != null) && mGoogleApiClient.isConnected()) { 160 161 Wearable.CapabilityApi.removeCapabilityListener( 162 mGoogleApiClient, 163 this, 164 CAPABILITY_WEAR_APP); 165 166 mGoogleApiClient.disconnect(); 167 } 168 } 169 170 @Override onResume()171 protected void onResume() { 172 Log.d(TAG, "onResume()"); 173 super.onResume(); 174 if (mGoogleApiClient != null) { 175 mGoogleApiClient.connect(); 176 } 177 } 178 179 @Override onConnected(@ullable Bundle bundle)180 public void onConnected(@Nullable Bundle bundle) { 181 Log.d(TAG, "onConnected()"); 182 183 // Set up listeners for capability changes (install/uninstall of remote app). 184 Wearable.CapabilityApi.addCapabilityListener( 185 mGoogleApiClient, 186 this, 187 CAPABILITY_WEAR_APP); 188 189 // Initial request for devices with our capability, aka, our Wear app installed. 190 findWearDevicesWithApp(); 191 192 // Initial request for all Wear devices connected (with or without our capability). 193 // Additional Note: Because there isn't a listener for ALL Nodes added/removed from network 194 // that isn't deprecated, we simply update the full list when the Google API Client is 195 // connected and when capability changes come through in the onCapabilityChanged() method. 196 findAllWearDevices(); 197 } 198 199 @Override onConnectionSuspended(int i)200 public void onConnectionSuspended(int i) { 201 Log.d(TAG, "onConnectionSuspended(): connection to location client suspended: " + i); 202 } 203 204 @Override onConnectionFailed(@onNull ConnectionResult connectionResult)205 public void onConnectionFailed(@NonNull ConnectionResult connectionResult) { 206 Log.e(TAG, "onConnectionFailed(): " + connectionResult); 207 } 208 209 /* 210 * Updates UI when capabilities change (install/uninstall wear app). 211 */ onCapabilityChanged(CapabilityInfo capabilityInfo)212 public void onCapabilityChanged(CapabilityInfo capabilityInfo) { 213 Log.d(TAG, "onCapabilityChanged(): " + capabilityInfo); 214 215 mWearNodesWithApp = capabilityInfo.getNodes(); 216 217 // Because we have an updated list of devices with/without our app, we need to also update 218 // our list of active Wear devices. 219 findAllWearDevices(); 220 221 verifyNodeAndUpdateUI(); 222 } 223 findWearDevicesWithApp()224 private void findWearDevicesWithApp() { 225 Log.d(TAG, "findWearDevicesWithApp()"); 226 227 // You can filter this by FILTER_REACHABLE if you only want to open Nodes (Wear Devices) 228 // directly connect to your phone. 229 PendingResult<CapabilityApi.GetCapabilityResult> pendingResult = 230 Wearable.CapabilityApi.getCapability( 231 mGoogleApiClient, 232 CAPABILITY_WEAR_APP, 233 CapabilityApi.FILTER_ALL); 234 235 pendingResult.setResultCallback(new ResultCallback<CapabilityApi.GetCapabilityResult>() { 236 @Override 237 public void onResult(@NonNull CapabilityApi.GetCapabilityResult getCapabilityResult) { 238 Log.d(TAG, "onResult(): " + getCapabilityResult); 239 240 if (getCapabilityResult.getStatus().isSuccess()) { 241 CapabilityInfo capabilityInfo = getCapabilityResult.getCapability(); 242 mWearNodesWithApp = capabilityInfo.getNodes(); 243 verifyNodeAndUpdateUI(); 244 245 } else { 246 Log.d(TAG, "Failed CapabilityApi: " + getCapabilityResult.getStatus()); 247 } 248 } 249 }); 250 } 251 findAllWearDevices()252 private void findAllWearDevices() { 253 Log.d(TAG, "findAllWearDevices()"); 254 255 PendingResult<NodeApi.GetConnectedNodesResult> pendingResult = 256 Wearable.NodeApi.getConnectedNodes(mGoogleApiClient); 257 258 pendingResult.setResultCallback(new ResultCallback<NodeApi.GetConnectedNodesResult>() { 259 @Override 260 public void onResult(@NonNull NodeApi.GetConnectedNodesResult getConnectedNodesResult) { 261 262 if (getConnectedNodesResult.getStatus().isSuccess()) { 263 mAllConnectedNodes = getConnectedNodesResult.getNodes(); 264 verifyNodeAndUpdateUI(); 265 266 } else { 267 Log.d(TAG, "Failed CapabilityApi: " + getConnectedNodesResult.getStatus()); 268 } 269 } 270 }); 271 } 272 verifyNodeAndUpdateUI()273 private void verifyNodeAndUpdateUI() { 274 Log.d(TAG, "verifyNodeAndUpdateUI()"); 275 276 if ((mWearNodesWithApp == null) || (mAllConnectedNodes == null)) { 277 Log.d(TAG, "Waiting on Results for both connected nodes and nodes with app"); 278 279 } else if (mAllConnectedNodes.isEmpty()) { 280 Log.d(TAG, NO_DEVICES); 281 mInformationTextView.setText(NO_DEVICES); 282 mRemoteOpenButton.setVisibility(View.INVISIBLE); 283 284 } else if (mWearNodesWithApp.isEmpty()) { 285 Log.d(TAG, MISSING_ALL_MESSAGE); 286 mInformationTextView.setText(MISSING_ALL_MESSAGE); 287 mRemoteOpenButton.setVisibility(View.VISIBLE); 288 289 } else if (mWearNodesWithApp.size() < mAllConnectedNodes.size()) { 290 // TODO: Add your code to communicate with the wear app(s) via 291 // Wear APIs (MessageApi, DataApi, etc.) 292 293 String installMessage = 294 String.format(INSTALLED_SOME_DEVICES_MESSAGE, mWearNodesWithApp); 295 Log.d(TAG, installMessage); 296 mInformationTextView.setText(installMessage); 297 mRemoteOpenButton.setVisibility(View.VISIBLE); 298 299 } else { 300 // TODO: Add your code to communicate with the wear app(s) via 301 // Wear APIs (MessageApi, DataApi, etc.) 302 303 String installMessage = 304 String.format(INSTALLED_ALL_DEVICES_MESSAGE, mWearNodesWithApp); 305 Log.d(TAG, installMessage); 306 mInformationTextView.setText(installMessage); 307 mRemoteOpenButton.setVisibility(View.INVISIBLE); 308 309 } 310 } 311 openPlayStoreOnWearDevicesWithoutApp()312 private void openPlayStoreOnWearDevicesWithoutApp() { 313 Log.d(TAG, "openPlayStoreOnWearDevicesWithoutApp()"); 314 315 // Create a List of Nodes (Wear devices) without your app. 316 ArrayList<Node> nodesWithoutApp = new ArrayList<>(); 317 318 for (Node node : mAllConnectedNodes) { 319 if (!mWearNodesWithApp.contains(node)) { 320 nodesWithoutApp.add(node); 321 } 322 } 323 324 if (!nodesWithoutApp.isEmpty()) { 325 Log.d(TAG, "Number of nodes without app: " + nodesWithoutApp.size()); 326 327 Intent intent = 328 new Intent(Intent.ACTION_VIEW) 329 .addCategory(Intent.CATEGORY_BROWSABLE) 330 .setData(Uri.parse(PLAY_STORE_APP_URI)); 331 332 for (Node node : nodesWithoutApp) { 333 RemoteIntent.startRemoteActivity( 334 getApplicationContext(), 335 intent, 336 mResultReceiver, 337 node.getId()); 338 } 339 } 340 } 341 }