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 }