| /* |
| * Copyright (C) 2016 Google Inc. All Rights Reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.example.android.wearable.wear.wearverifyremoteapp; |
| |
| import android.content.Intent; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.ResultReceiver; |
| import android.support.wearable.phone.PhoneDeviceType; |
| import android.support.wearable.view.ConfirmationOverlay; |
| import android.util.Log; |
| import android.view.View; |
| import android.widget.Button; |
| import android.widget.TextView; |
| |
| import androidx.fragment.app.FragmentActivity; |
| import androidx.wear.ambient.AmbientModeSupport; |
| |
| import com.google.android.gms.tasks.OnCompleteListener; |
| import com.google.android.gms.tasks.Task; |
| import com.google.android.gms.wearable.CapabilityClient; |
| import com.google.android.gms.wearable.CapabilityInfo; |
| import com.google.android.gms.wearable.Node; |
| import com.google.android.gms.wearable.Wearable; |
| import com.google.android.wearable.intent.RemoteIntent; |
| |
| import java.util.Set; |
| |
| /** |
| * Checks if the phone app is installed on remote device. If it is not, allows user to open app |
| * listing on the phone's Play or App Store. |
| */ |
| public class MainWearActivity extends FragmentActivity implements |
| AmbientModeSupport.AmbientCallbackProvider, |
| CapabilityClient.OnCapabilityChangedListener { |
| |
| private static final String TAG = "MainWearActivity"; |
| |
| private static final String WELCOME_MESSAGE = "Welcome to our Wear app!\n\n"; |
| |
| private static final String CHECKING_MESSAGE = |
| WELCOME_MESSAGE + "Checking for Mobile app...\n"; |
| |
| private static final String MISSING_MESSAGE = |
| WELCOME_MESSAGE |
| + "You are missing the required phone app, please click on the button below to " |
| + "install it on your phone.\n"; |
| |
| private static final String INSTALLED_MESSAGE = |
| WELCOME_MESSAGE |
| + "Mobile app installed on your %s!\n\nYou can now use MessageApi, " |
| + "DataApi, etc."; |
| |
| // Name of capability listed in Phone app's wear.xml. |
| // IMPORTANT NOTE: This should be named differently than your Wear app's capability. |
| private static final String CAPABILITY_PHONE_APP = "verify_remote_example_phone_app"; |
| |
| // Links to install mobile app for both Android (Play Store) and iOS. |
| // TODO: Replace with your links/packages. |
| private static final String ANDROID_MARKET_APP_URI = |
| "market://details?id=com.example.android.wearable.wear.wearverifyremoteapp"; |
| |
| // TODO: Replace with your links/packages. |
| private static final String APP_STORE_APP_URI = |
| "https://itunes.apple.com/us/app/android-wear/id986496028?mt=8"; |
| |
| // Result from sending RemoteIntent to phone to open app in play/app store. |
| private final ResultReceiver mResultReceiver = new ResultReceiver(new Handler()) { |
| @Override |
| protected void onReceiveResult(int resultCode, Bundle resultData) { |
| |
| if (resultCode == RemoteIntent.RESULT_OK) { |
| new ConfirmationOverlay().showOn(MainWearActivity.this); |
| |
| } else if (resultCode == RemoteIntent.RESULT_FAILED) { |
| new ConfirmationOverlay() |
| .setType(ConfirmationOverlay.FAILURE_ANIMATION) |
| .showOn(MainWearActivity.this); |
| |
| } else { |
| throw new IllegalStateException("Unexpected result " + resultCode); |
| } |
| } |
| }; |
| |
| private TextView mInformationTextView; |
| private Button mRemoteOpenButton; |
| |
| private Node mAndroidPhoneNodeWithApp; |
| |
| @Override |
| protected void onCreate(Bundle savedInstanceState) { |
| Log.d(TAG, "onCreate()"); |
| super.onCreate(savedInstanceState); |
| |
| setContentView(R.layout.activity_main); |
| |
| // Enables Ambient mode. |
| AmbientModeSupport.attach(this); |
| |
| mInformationTextView = findViewById(R.id.information_text_view); |
| mRemoteOpenButton = findViewById(R.id.remote_open_button); |
| |
| mInformationTextView.setText(CHECKING_MESSAGE); |
| |
| mRemoteOpenButton.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View view) { |
| openAppInStoreOnPhone(); |
| } |
| }); |
| } |
| |
| |
| @Override |
| protected void onPause() { |
| Log.d(TAG, "onPause()"); |
| super.onPause(); |
| |
| Wearable.getCapabilityClient(this).removeListener(this, CAPABILITY_PHONE_APP); |
| } |
| |
| @Override |
| protected void onResume() { |
| Log.d(TAG, "onResume()"); |
| super.onResume(); |
| |
| Wearable.getCapabilityClient(this).addListener(this, CAPABILITY_PHONE_APP); |
| |
| checkIfPhoneHasApp(); |
| } |
| |
| /* |
| * Updates UI when capabilities change (install/uninstall phone app). |
| */ |
| public void onCapabilityChanged(CapabilityInfo capabilityInfo) { |
| Log.d(TAG, "onCapabilityChanged(): " + capabilityInfo); |
| |
| mAndroidPhoneNodeWithApp = pickBestNodeId(capabilityInfo.getNodes()); |
| verifyNodeAndUpdateUI(); |
| } |
| |
| private void checkIfPhoneHasApp() { |
| Log.d(TAG, "checkIfPhoneHasApp()"); |
| |
| Task<CapabilityInfo> capabilityInfoTask = Wearable.getCapabilityClient(this) |
| .getCapability(CAPABILITY_PHONE_APP, CapabilityClient.FILTER_ALL); |
| |
| capabilityInfoTask.addOnCompleteListener(new OnCompleteListener<CapabilityInfo>() { |
| @Override |
| public void onComplete(Task<CapabilityInfo> task) { |
| |
| if (task.isSuccessful()) { |
| Log.d(TAG, "Capability request succeeded."); |
| CapabilityInfo capabilityInfo = task.getResult(); |
| mAndroidPhoneNodeWithApp = pickBestNodeId(capabilityInfo.getNodes()); |
| |
| } else { |
| Log.d(TAG, "Capability request failed to return any results."); |
| } |
| |
| verifyNodeAndUpdateUI(); |
| } |
| }); |
| } |
| |
| private void verifyNodeAndUpdateUI() { |
| |
| if (mAndroidPhoneNodeWithApp != null) { |
| |
| // TODO: Add your code to communicate with the phone app via |
| // Wear APIs (MessageApi, DataApi, etc.) |
| |
| String installMessage = |
| String.format(INSTALLED_MESSAGE, mAndroidPhoneNodeWithApp.getDisplayName()); |
| Log.d(TAG, installMessage); |
| mInformationTextView.setText(installMessage); |
| mRemoteOpenButton.setVisibility(View.INVISIBLE); |
| |
| } else { |
| Log.d(TAG, MISSING_MESSAGE); |
| mInformationTextView.setText(MISSING_MESSAGE); |
| mRemoteOpenButton.setVisibility(View.VISIBLE); |
| } |
| } |
| |
| private void openAppInStoreOnPhone() { |
| Log.d(TAG, "openAppInStoreOnPhone()"); |
| |
| int phoneDeviceType = PhoneDeviceType.getPhoneDeviceType(getApplicationContext()); |
| switch (phoneDeviceType) { |
| // Paired to Android phone, use Play Store URI. |
| case PhoneDeviceType.DEVICE_TYPE_ANDROID: |
| Log.d(TAG, "\tDEVICE_TYPE_ANDROID"); |
| // Create Remote Intent to open Play Store listing of app on remote device. |
| Intent intentAndroid = |
| new Intent(Intent.ACTION_VIEW) |
| .addCategory(Intent.CATEGORY_BROWSABLE) |
| .setData(Uri.parse(ANDROID_MARKET_APP_URI)); |
| |
| RemoteIntent.startRemoteActivity( |
| getApplicationContext(), |
| intentAndroid, |
| mResultReceiver); |
| break; |
| |
| // Paired to iPhone, use iTunes App Store URI |
| case PhoneDeviceType.DEVICE_TYPE_IOS: |
| Log.d(TAG, "\tDEVICE_TYPE_IOS"); |
| |
| // Create Remote Intent to open App Store listing of app on iPhone. |
| Intent intentIOS = |
| new Intent(Intent.ACTION_VIEW) |
| .addCategory(Intent.CATEGORY_BROWSABLE) |
| .setData(Uri.parse(APP_STORE_APP_URI)); |
| |
| RemoteIntent.startRemoteActivity( |
| getApplicationContext(), |
| intentIOS, |
| mResultReceiver); |
| break; |
| |
| case PhoneDeviceType.DEVICE_TYPE_ERROR_UNKNOWN: |
| Log.d(TAG, "\tDEVICE_TYPE_ERROR_UNKNOWN"); |
| break; |
| } |
| } |
| |
| /* |
| * There should only ever be one phone in a node set (much less w/ the correct capability), so |
| * I am just grabbing the first one (which should be the only one). |
| */ |
| private Node pickBestNodeId(Set<Node> nodes) { |
| Log.d(TAG, "pickBestNodeId(): " + nodes); |
| |
| Node bestNodeId = null; |
| // Find a nearby node/phone or pick one arbitrarily. Realistically, there is only one phone. |
| for (Node node : nodes) { |
| bestNodeId = node; |
| } |
| return bestNodeId; |
| } |
| |
| @Override |
| public AmbientModeSupport.AmbientCallback getAmbientCallback() { |
| return new MyAmbientCallback(); |
| } |
| |
| private class MyAmbientCallback extends AmbientModeSupport.AmbientCallback { |
| /** Prepares the UI for ambient mode. */ |
| @Override |
| public void onEnterAmbient(Bundle ambientDetails) { |
| super.onEnterAmbient(ambientDetails); |
| |
| Log.d(TAG, "onEnterAmbient() " + ambientDetails); |
| // In our case, the assets are already in black and white, so we don't update UI. |
| } |
| |
| /** Restores the UI to active (non-ambient) mode. */ |
| @Override |
| public void onExitAmbient() { |
| super.onExitAmbient(); |
| |
| Log.d(TAG, "onExitAmbient()"); |
| // In our case, the assets are already in black and white, so we don't update UI. |
| } |
| } |
| } |