Skip to content
This repository was archived by the owner on Jan 1, 2023. It is now read-only.

Commit 2ff8cc4

Browse files
Merge remote-tracking branch 'origin/wear2' into wear2
# Conflicts: # build.gradle # gradle.properties # mobile/build.gradle # wear/build.gradle
2 parents 2e84cc9 + 5e8436b commit 2ff8cc4

File tree

5 files changed

+278
-1
lines changed

5 files changed

+278
-1
lines changed

mobile/src/main/AndroidManifest.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@
4949
</intent-filter>
5050
</receiver>
5151
<!-- End Locale specifics -->
52+
53+
<meta-data
54+
android:name="com.google.android.gms.version"
55+
android:value="@integer/google_play_services_version" />
5256
</application>
5357

5458
</manifest>

mobile/src/main/java/org/codechimp/ttw/MainActivity.java

Lines changed: 254 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,79 @@
11
package org.codechimp.ttw;
22

3+
import android.content.DialogInterface;
4+
import android.content.Intent;
35
import android.content.pm.PackageManager;
6+
import android.net.Uri;
47
import android.os.Bundle;
8+
import android.os.Handler;
9+
import android.os.ResultReceiver;
10+
import android.support.annotation.NonNull;
11+
import android.support.annotation.Nullable;
512
import android.support.v4.app.Fragment;
13+
import android.support.v7.app.AlertDialog;
614
import android.support.v7.app.AppCompatActivity;
715
import android.support.v7.widget.Toolbar;
16+
import android.util.Log;
817
import android.view.LayoutInflater;
918
import android.view.Menu;
1019
import android.view.MenuItem;
1120
import android.view.View;
1221
import android.view.ViewGroup;
1322
import android.widget.Button;
23+
import android.widget.Toast;
1424

15-
public class MainActivity extends AppCompatActivity {
25+
import com.google.android.gms.common.ConnectionResult;
26+
import com.google.android.gms.common.api.GoogleApiClient;
27+
import com.google.android.gms.common.api.PendingResult;
28+
import com.google.android.gms.common.api.ResultCallback;
29+
import com.google.android.gms.wearable.CapabilityApi;
30+
import com.google.android.gms.wearable.CapabilityInfo;
31+
import com.google.android.gms.wearable.Node;
32+
import com.google.android.gms.wearable.NodeApi;
33+
import com.google.android.gms.wearable.Wearable;
34+
import com.google.android.wearable.intent.RemoteIntent;
35+
36+
import java.util.ArrayList;
37+
import java.util.List;
38+
import java.util.Set;
39+
40+
public class MainActivity extends AppCompatActivity
41+
implements
42+
GoogleApiClient.ConnectionCallbacks,
43+
GoogleApiClient.OnConnectionFailedListener,
44+
CapabilityApi.CapabilityListener {
45+
46+
private static final String TAG = "TTW";
47+
48+
private GoogleApiClient googleApiClient;
49+
50+
// Name of capability listed in Wear app's wear.xml.
51+
// IMPORTANT NOTE: This should be named differently than your Phone app's capability.
52+
private static final String CAPABILITY_WEAR_APP = "verify_remote_ttw_wear_app";
53+
54+
private Set<Node> wearNodesWithApp;
55+
private List<Node> allConnectedNodes;
56+
57+
// Result from sending RemoteIntent to wear device(s) to open app in play/app store.
58+
private final ResultReceiver resultReceiver = new ResultReceiver(new Handler()) {
59+
@Override
60+
protected void onReceiveResult(int resultCode, Bundle resultData) {
61+
Log.d(TAG, "onReceiveResult: " + resultCode);
62+
63+
if (resultCode == RemoteIntent.RESULT_OK) {
64+
// Do nothing
65+
} else if (resultCode == RemoteIntent.RESULT_FAILED) {
66+
Toast toast = Toast.makeText(
67+
getApplicationContext(),
68+
R.string.no_watch_app_remote_error,
69+
Toast.LENGTH_LONG);
70+
toast.show();
71+
72+
} else {
73+
throw new IllegalStateException("Unexpected result " + resultCode);
74+
}
75+
}
76+
};
1677

1778
@Override
1879
protected void onCreate(Bundle savedInstanceState) {
@@ -39,5 +100,197 @@ public void onClick(View v) {
39100
finish();
40101
}
41102
});
103+
104+
googleApiClient = new GoogleApiClient.Builder(this)
105+
.addApi(Wearable.API)
106+
.addConnectionCallbacks(this)
107+
.build();
108+
}
109+
110+
@Override
111+
protected void onStop() {
112+
googleApiClient.disconnect();
113+
super.onStop();
114+
}
115+
116+
@Override
117+
protected void onPause() {
118+
super.onPause();
119+
120+
if ((googleApiClient != null) && googleApiClient.isConnected()) {
121+
122+
Wearable.CapabilityApi.removeCapabilityListener(
123+
googleApiClient,
124+
this,
125+
CAPABILITY_WEAR_APP);
126+
127+
googleApiClient.disconnect();
128+
}
129+
}
130+
131+
@Override
132+
protected void onResume() {
133+
super.onResume();
134+
135+
if (googleApiClient != null) {
136+
googleApiClient.connect();
137+
}
138+
}
139+
140+
@Override
141+
public void onConnected(@Nullable Bundle bundle) {
142+
Log.d(TAG, "onConnected()");
143+
144+
// Set up listeners for capability changes (install/uninstall of remote app).
145+
Wearable.CapabilityApi.addCapabilityListener(
146+
googleApiClient,
147+
this,
148+
CAPABILITY_WEAR_APP);
149+
150+
// Initial request for devices with our capability, aka, our Wear app installed.
151+
findWearDevicesWithApp();
152+
153+
// Initial request for all Wear devices connected (with or without our capability).
154+
// Additional Note: Because there isn't a listener for ALL Nodes added/removed from network
155+
// that isn't deprecated, we simply update the full list when the Google API Client is
156+
// connected and when capability changes come through in the onCapabilityChanged() method.
157+
findAllWearDevices();
158+
}
159+
160+
161+
@Override
162+
public void onConnectionSuspended(int i) {
163+
Log.d(TAG, "onConnectionSuspended(): connection to location client suspended: " + i);
164+
}
165+
166+
@Override
167+
public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
168+
Log.e(TAG, "onConnectionFailed(): " + connectionResult);
169+
}
170+
171+
@Override
172+
public void onCapabilityChanged(CapabilityInfo capabilityInfo) {
173+
Log.d(TAG, "onCapabilityChanged(): " + capabilityInfo);
174+
175+
wearNodesWithApp = capabilityInfo.getNodes();
176+
177+
// Because we have an updated list of devices with/without our app, we need to also update
178+
// our list of active Wear devices.
179+
findAllWearDevices();
180+
181+
verifyNodeAndUpdateUI();
182+
}
183+
184+
private void findWearDevicesWithApp() {
185+
Log.d(TAG, "findWearDevicesWithApp()");
186+
187+
// You can filter this by FILTER_REACHABLE if you only want to open Nodes (Wear Devices)
188+
// directly connect to your phone.
189+
PendingResult<CapabilityApi.GetCapabilityResult> pendingResult =
190+
Wearable.CapabilityApi.getCapability(
191+
googleApiClient,
192+
CAPABILITY_WEAR_APP,
193+
CapabilityApi.FILTER_ALL);
194+
195+
pendingResult.setResultCallback(new ResultCallback<CapabilityApi.GetCapabilityResult>() {
196+
@Override
197+
public void onResult(@NonNull CapabilityApi.GetCapabilityResult getCapabilityResult) {
198+
Log.d(TAG, "onResult(): " + getCapabilityResult);
199+
200+
if (getCapabilityResult.getStatus().isSuccess()) {
201+
CapabilityInfo capabilityInfo = getCapabilityResult.getCapability();
202+
wearNodesWithApp = capabilityInfo.getNodes();
203+
verifyNodeAndUpdateUI();
204+
205+
} else {
206+
Log.d(TAG, "Failed CapabilityApi: " + getCapabilityResult.getStatus());
207+
}
208+
}
209+
});
210+
}
211+
212+
private void findAllWearDevices() {
213+
Log.d(TAG, "findAllWearDevices()");
214+
215+
PendingResult<NodeApi.GetConnectedNodesResult> pendingResult =
216+
Wearable.NodeApi.getConnectedNodes(googleApiClient);
217+
218+
pendingResult.setResultCallback(new ResultCallback<NodeApi.GetConnectedNodesResult>() {
219+
@Override
220+
public void onResult(@NonNull NodeApi.GetConnectedNodesResult getConnectedNodesResult) {
221+
222+
if (getConnectedNodesResult.getStatus().isSuccess()) {
223+
allConnectedNodes = getConnectedNodesResult.getNodes();
224+
verifyNodeAndUpdateUI();
225+
226+
} else {
227+
Log.d(TAG, "Failed CapabilityApi: " + getConnectedNodesResult.getStatus());
228+
}
229+
}
230+
});
231+
}
232+
233+
private void verifyNodeAndUpdateUI() {
234+
Log.d(TAG, "verifyNodeAndUpdateUI()");
235+
236+
if ((wearNodesWithApp == null) || (allConnectedNodes == null)) {
237+
Log.d(TAG, "Waiting on Results for both connected nodes and nodes with app");
238+
239+
} else if (allConnectedNodes.isEmpty()) {
240+
Log.d(TAG, "No wear devices");
241+
242+
} else if (wearNodesWithApp.isEmpty()) {
243+
Log.d(TAG, "No devices with app");
244+
showRemoteInstallDialog();
245+
246+
} else if (wearNodesWithApp.size() < allConnectedNodes.size()) {
247+
Log.d(TAG, String.format("Installed on some devices (%s)", wearNodesWithApp));
248+
} else {
249+
Log.d(TAG, String.format("Installed on all devices (%s)", wearNodesWithApp));
250+
}
251+
}
252+
253+
protected void showRemoteInstallDialog() {
254+
new AlertDialog.Builder(this)
255+
.setTitle(R.string.no_watch_app_title)
256+
.setMessage(R.string.no_watch_app_description)
257+
.setNegativeButton(android.R.string.cancel, null)
258+
.setPositiveButton(R.string.no_watch_app_button, new DialogInterface.OnClickListener() {
259+
@Override
260+
public void onClick(DialogInterface dialog, int which) {
261+
openPlayStoreOnWearDevicesWithoutApp();
262+
}
263+
})
264+
.show();
265+
}
266+
267+
private void openPlayStoreOnWearDevicesWithoutApp() {
268+
Log.d(TAG, "openPlayStoreOnWearDevicesWithoutApp()");
269+
270+
// Create a List of Nodes (Wear devices) without your app.
271+
ArrayList<Node> nodesWithoutApp = new ArrayList<>();
272+
273+
for (Node node : allConnectedNodes) {
274+
if (!wearNodesWithApp.contains(node)) {
275+
nodesWithoutApp.add(node);
276+
}
277+
}
278+
279+
if (!nodesWithoutApp.isEmpty()) {
280+
Log.d(TAG, "Number of nodes without app: " + nodesWithoutApp.size());
281+
282+
Intent intent =
283+
new Intent(Intent.ACTION_VIEW)
284+
.addCategory(Intent.CATEGORY_BROWSABLE)
285+
.setData(Uri.parse("market://details?id=" + getPackageName()));
286+
287+
for (Node node : nodesWithoutApp) {
288+
RemoteIntent.startRemoteActivity(
289+
getApplicationContext(),
290+
intent,
291+
resultReceiver,
292+
node.getId());
293+
}
294+
}
42295
}
43296
}

mobile/src/main/res/values/strings.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,8 @@
3838
<string name="try_pattern">Try</string>
3939
<string name="tap_pattern">Tap a pattern</string>
4040

41+
<string name="no_watch_app_title">Watch app not installed</string>
42+
<string name="no_watch_app_description">Watch companion is not installed. This app will not work properly.\n\nIf your watch is running Android Wear 1.x, then you do not have to do anything, app will be installed automatically in few minutes at most.\n\nIf your watch is running Android Wear 2, please click \'Open Store\' button below to open Play Store on the watch and install companion app.</string>
43+
<string name="no_watch_app_button">Open store</string>
44+
<string name="no_watch_app_remote_error">Play Store Request Failed. Wear device(s) may not support Play Store, that is, the Wear device may be version 1.0.</string>
4145
</resources>

mobile/src/main/res/values/wear.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
3+
<resources>
4+
<string-array name="android_wear_capabilities" translatable="false">
5+
<!-- IMPORTANT NOTE: Should be different than capability in wear res/values/wear.xml. -->
6+
<item>verify_remote_ttw_phone_app</item>
7+
</string-array>
8+
</resources>

wear/src/main/res/values/wear.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
3+
<resources>
4+
<string-array name="android_wear_capabilities" translatable="false">
5+
<!-- IMPORTANT NOTE: Should be different than capability in phone res/values/wear.xml. -->
6+
<item>verify_remote_ttw_wear_app</item>
7+
</string-array>
8+
</resources>

0 commit comments

Comments
 (0)