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 }