original source : https://developer.android.com/training/wearables/wearable-sounds.html


Using Speakers on Wearables

Detect the Speaker

the app uses the getDevices() method in conjunction with the value of FEATURE_AUDIO_OUTPUT to confirm that the device is equipped with a speaker.

PackageManager packageManager = context.getPackageManager();
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);

// Check whether the device has a speaker.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
   // Check FEATURE_AUDIO_OUTPUT to guard against false positives.
   if (!packageManager.hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
       return false;
   }

   AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
   for (AudioDeviceInfo device : devices) {
       if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
           return true;
       }
   }
}
return false;

Play Sounds

the process for playing sound on Android Wear is the same as for a handset or other device. For more information, see Media Playback.

If you also want to record audio from the microphone on the wearable, your app must also get permission to use the microphone. To learn more, seePermissions on Android Wear.

original source : https://developer.android.com/training/wearables/data-layer/events.html

Handling Data Layer Events

When you make a call to the Data Layer API, you can receive the status of the call when it completes.

Note: A Wear app can communicate with a phone app using the Data Layer API, but connecting to a network using this API is discouraged

Wait for the Status of Data Layer Calls

때때로 

Data Layer API call 작업(예를 들어 

putDataItem()

)은 

PendingResult를 결과로 되돌린다. 이때 이 

PendingResult 를 처리하는 방법에는 두가지가 있다.  (synchronously or asynchronously)

          Asynchronous calls(main thread에서 호출하는 경우)

If your code is running on the main UI thread, do not make blocking calls to the Data Layer API. You can run the calls asynchronously by adding a callback method to the PendingResult object, which fires when the operation is completed(버튼 클릭리스너를 만들어 버튼에 연결하듯 처리하는 방법)

pendingResult.setResultCallback(new ResultCallback<DataItemResult>() {
   @Override
   public void onResult(final DataItemResult result) {
       if(result.getStatus().isSuccess()) {
           Log.d(TAG, "Data item set: " + result.getDataItem().getUri());
       }
   }
});

          Synchronous calls(다른 thread에서 호출하는 경우)

If your code is running on a separate handler thread in a background service (which is the case in a WearableListenerService), it’s fine for the calls to block. In this case, you can call await() on the PendingResult object, which blocks until the request completes and returns a Result object:

DataItemResult result = pendingResult.await();
if(result.getStatus().isSuccess()) {
   Log.d(TAG, "Data item set: " + result.getDataItem().getUri());
}

Listen for Data Layer Events

data layer synchronizes and sends data across the handheld and wearable.

To listen for data layer events, you have two options:

          With a WearableListenerService

You typically create instances of this service in both your wearable and handheld apps. If you are not interested in data events in one of these apps, then you don’t need to implement this service in that particular app.

Some of the events you can listen for using WearableListenerService are as follows:

  • onDataChanged(): Whenever a data item object is created, deleted, or changed, the system triggers this callback on all connected nodes.
  • onMessageReceived(): A message sent from a node triggers this callback on the target node.
  • onCapabilityChanged(): When a capability that an instance of your app advertises becomes available on the network, that event triggers this callback. If you’re looking for a nearby node you can query the isNearby() method of the nodes provided in the callback.

In addition to those on this list, you can listen for events from ChannelApi.ChannelListener, such as onChannelOpened().

All of the above events are executed in a background thread, not on the main thread.

To create a WearableListenerService, follow these steps:

  1. Create a class that extends WearableListenerService.
  2. Listen for the events that you’re interested in, such as onDataChanged().
  3. Declare an intent filter in your Android manifest to notify the system about your WearableListenerService. This declaration allows the system to bind your service as needed.
public class DataLayerListenerService extends WearableListenerService {

   private static final String TAG = "DataLayerSample";
   private static final String START_ACTIVITY_PATH = "/start-activity";
   private static final String DATA_ITEM_RECEIVED_PATH = "/data-item-received";

   @Override
   public void onDataChanged(DataEventBuffer dataEvents) {
       if (Log.isLoggable(TAG, Log.DEBUG)) {
           Log.d(TAG, "onDataChanged: " + dataEvents);
       }

       GoogleApiClient googleApiClient = new GoogleApiClient.Builder(this)
               .addApi(Wearable.API)
               .build();

       ConnectionResult connectionResult =
               googleApiClient.blockingConnect(30, TimeUnit.SECONDS);

       if (!connectionResult.isSuccess()) {
           Log.e(TAG, "Failed to connect to GoogleApiClient.");
           return;
       }

       // Loop through the events and send a message
       // to the node that created the data item.
       for (DataEvent event : dataEvents) {
           Uri uri = event.getDataItem().getUri();

           // Get the node id from the host value of the URI
           String nodeId = uri.getHost();
           // Set the data of the message to be the bytes of the URI
           byte[] payload = uri.toString().getBytes();

           // Send the RPC
           Wearable.MessageApi.sendMessage(googleApiClient, nodeId,
                   DATA_ITEM_RECEIVED_PATH, payload);
       }
   }
}

          Using filters with WearableListenerService

<service android:name=".DataLayerListenerService">
 <intent-filter>
     <action android:name="com.google.android.gms.wearable.DATA_CHANGED" />
     <data android:scheme="wear" android:host="*"
              android:path="/start-activity" />
 </intent-filter>
</service>

manifest에 위와 같이 작성한 서비스를 등록하는 과정설명. 위의 경우 the watch listens for the /start-activity data item, and the phone listens for the /data-item-received message response. 

To match on a wildcard host, use host="*". To match on a specific host, specify host=<node_id>.

For more information on the filter types that Wear supports, see the API reference documentation for WearableListenerService.

For more information on data filters and matching rules, see the API reference documentation for the data manifest element.

When matching intent filters, there are two important rules to remember:

  • If a scheme is not specified for the intent filter, the system ignores all the other URI attributes.
  • If no host is specified for the filter, the system ignores all the path attributes.

          

With a live listener

If your app only cares about data-layer events when the user is interacting with the app, it may not need a long-running service to handle every data change. In such a case, you can listen for events in an activity by implementing one or more of the following interfaces:

To create an activity that listens for data events:

An alternative to adding listeners in onConnected() and removing them in onStop() is to add a filtered listener in an activity’s onResume() and remove it in onPause(), to only receive data that is relevant to the current application state.

public class MainActivity extends Activity implements
       DataApi.DataListener, ConnectionCallbacks, OnConnectionFailedListener {

   private GoogleApiClient mGoogleApiClient;

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);

       setContentView(R.layout.main);
       mGoogleApiClient = new GoogleApiClient.Builder(this)
               .addApi(Wearable.API)
               .addConnectionCallbacks(this)
               .addOnConnectionFailedListener(this)
               .build();
   }

   @Override
   protected void onStart() {
       super.onStart();
       if (!mResolvingError) {
           mGoogleApiClient.connect();
       }
   }

   @Override
   public void onConnected(Bundle connectionHint) {
       if (Log.isLoggable(TAG, Log.DEBUG)) {
           Log.d(TAG, "Connected to Google Api Service");
       }
       Wearable.DataApi.addListener(mGoogleApiClient, this);
   }

   @Override
   protected void onStop() {
       if (null != mGoogleApiClient && mGoogleApiClient.isConnected()) {
           Wearable.DataApi.removeListener(mGoogleApiClient, this);
           mGoogleApiClient.disconnect();
       }
       super.onStop();
   }

   @Override
   public void onDataChanged(DataEventBuffer dataEvents) {
       for (DataEvent event : dataEvents) {
           if (event.getType() == DataEvent.TYPE_DELETED) {
               Log.d(TAG, "DataItem deleted: " + event.getDataItem().getUri());
           } else if (event.getType() == DataEvent.TYPE_CHANGED) {
               Log.d(TAG, "DataItem changed: " + event.getDataItem().getUri());
           }
       }
   }
}



Using filters with live listeners

Just as you can specify intent filters for manifest-based WearableListenerService objects, you can also use intent filters when registering a listener through the Wearable API. The same rules are applicable to both API-based listeners manifest-based listeners.

A common pattern is to register a listener with a specific path or path prefix in an activity’s onResume() method, and to remove the listener in the activity’s onPause() method. Implementing listeners in this fashion allows your app to more selectively receive events, improving its design and efficiency.

original source : https://developer.android.com/training/wearables/data-layer/messages.html

Sending and Receiving Messages

You send messages using the MessageApi and attach the following items to the message:

  • An arbitrary payload (optional)
  • A path that uniquely identifies the message’s action

syncing 은 양방향이라면 message는 일방이며 

remote procedure calls (RPC)에 유용하다. 예를 들어 sending a message to the wearable to start an activity.

Multiple wearable devices can be connected to a user’s handheld device. Each connected device in the network is considered a node. 그러므로 message를 보내는 경우 목적지를 명확하게 해야 한다. google play 7.3 이전에는 하나의 mobile에 하나의 wearable기기만 연결할수 있으나 그 이후 버전에는 여러개를 연결해서 사용가능하므로 새로운 기능에대한 업데이트를 잘못하면 제대로 전달되지 않는 문제가 발생할수 있다.

Send a Message

       

Advertise capabilities

To launch an activity on a handheld device from a wearable device, use the MessageApi class to send the request.

the wearable app needs to determine that a connected node is capable of launching the activity. wearable app에서 작업을 수행할수 있는지 확인 하는 과정에서 작업을 수행할수 있는 기기의 app에서는 wearable app에게 본기기가 작업수행이 가능하다는 것을 알려야 하는 데 그방법은 아래와 같다.

  1. Create an XML configuration file in the res/values/ directory of your project and name it wear.xml.
  2. Add a resource named android_wear_capabilities to wear.xml.
  3. Define capabilities that the device provides.

Note: Capabilities are custom strings that you define and must be unique within your app.

<resources>
   <string-array name="android_wear_capabilities">
       <item>voice_transcription</item>
   </string-array>
</resources>

        Retrieve the nodes with the required capabilities (어느 app이 작업을 처리가능한지 확인하는 과정)

CapabilityApi.getCapability() method 를 이용한다.

private static final String
       VOICE_TRANSCRIPTION_CAPABILITY_NAME = "voice_transcription";

private GoogleApiClient mGoogleApiClient;

...

private void setupVoiceTranscription() {
   CapabilityApi.GetCapabilityResult result =
           Wearable.CapabilityApi.getCapability(
                   mGoogleApiClient, VOICE_TRANSCRIPTION_CAPABILITY_NAME,
                   CapabilityApi.FILTER_REACHABLE).await();

   updateTranscriptionCapability(result.getCapability());
}

To detect capable nodes as they connect to the wearable device, register a CapabilityApi.CapabilityListener() instance to your GoogleApiClient.

private void setupVoiceTranscription() {
   ...

   CapabilityApi.CapabilityListener capabilityListener =
           new CapabilityApi.CapabilityListener() {
               @Override
               public void onCapabilityChanged(CapabilityInfo capabilityInfo) {
                   updateTranscriptionCapability(capabilityInfo);
               }
           };

   Wearable.CapabilityApi.addCapabilityListener(
           mGoogleApiClient,
           capabilityListener,
           VOICE_TRANSCRIPTION_CAPABILITY_NAME);
}

Note: If you create a service that extends WearableListenerService to detect capability changes, you may want to override the onConnectedNodes()method to listen to finer-grained connectivity details, such as when a wearable device switches from Wi-Fi to a Bluetooth connection to the handset. For more information on how to listen for important events, see Listen for Data Layer Events.

After detecting the capable nodes, determine where to send the message. You should pick a node that is in close proximity to your wearable device to minimize message routing through multiple nodes. A nearby node is defined as one that is directly connected to the device. To determine if a node is nearby, call the Node.isNearby() method.

private String transcriptionNodeId = null;

private void updateTranscriptionCapability(CapabilityInfo capabilityInfo) {
   Set<Node> connectedNodes = capabilityInfo.getNodes();

   transcriptionNodeId = pickBestNodeId(connectedNodes);
}

private String pickBestNodeId(Set<Node> nodes) {
   String bestNodeId = null;
   // Find a nearby node or pick one arbitrarily
   for (Node node : nodes) {
       if (node.isNearby()) {
           return node.getId();
        }
        bestNodeId = node.getId();
   }
   return bestNodeId;
}

       Deliver the message

Once you’ve identified the best node to use, send the message using the MessageApi class.

Verify that the node is available before you attempt to send the message. This call is synchronous and blocks processing until the system queues the message for delivery.

Note: A successful result code does not guarantee delivery of the message. If your app requires data reliability, consider using DataItem objects or the ChannelApi class to send data between devices.

public static final String VOICE_TRANSCRIPTION_MESSAGE_PATH = "/voice_transcription";

private void requestTranscription(byte[] voiceData) {
   if (transcriptionNodeId != null) {
       Wearable.MessageApi.sendMessage(googleApiClient, transcriptionNodeId,
           VOICE_TRANSCRIPTION_MESSAGE_PATH, voiceData).setResultCallback(
                 new ResultCallback() {
                     @Override
                     public void onResult(SendMessageResult sendMessageResult) {
                         if (!sendMessageResult.getStatus().isSuccess()) {
                             // Failed to send message
                         }
                     }
                 }
           );
   } else {
       // Unable to retrieve node with transcription capability
   }
}

Note: To learn more about asynchronous and synchronous calls to Google Play services and when to use each, see Communicate with Google Play Services.

You can also broadcast messages to all connected nodes.

private Collection<String> getNodes() {
   HashSet <String>results = new HashSet<String>();
   NodeApi.GetConnectedNodesResult nodes =
           Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).await();
   for (Node node : nodes.getNodes()) {
       results.add(node.getId());
   }
   return results;
}

Receive a Message

To be notified of received messages, implement the MessageListener interface to provide a listener for message events. Then, register the listener with the MessageApi.addListener() method.

@Override
public void onMessageReceived(MessageEvent messageEvent) {
   if (messageEvent.getPath().equals(VOICE_TRANSCRIPTION_MESSAGE_PATH)) {
       Intent startIntent = new Intent(this, MainActivity.class);
       startIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
       startIntent.putExtra("VOICE_DATA", messageEvent.getData());
       startActivity(startIntent);
   }
}

original source : https://developer.android.com/training/wearables/data-layer/assets.html

Transferring Assets

image와 같이 크기가 큰 data를 전달하는 경우, you can attach an Asset to a data item and the put the data item into the replicated data store.

data item의 경우 최대 크기가 100kb 이나 assets의 경우에는 그 제한이 없다.

Transfer an Asset

Create the asset using one of the create...() methods in the Asset class.    

private static Asset createAssetFromBitmap(Bitmap bitmap) {

final ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
    bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteStream);
    return Asset.createFromBytes(byteStream.toByteArray());
}

Using PutDataRequest

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image);
Asset asset = createAssetFromBitmap(bitmap);
PutDataRequest request = PutDataRequest.create("/image");
request.putAsset("profileImage", asset);
Wearable.DataApi.putDataItem(mGoogleApiClient, request);

Using PutDataMapRequest

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image);
Asset asset = createAssetFromBitmap(bitmap);
PutDataMapRequest dataMap = PutDataMapRequest.create("/image");
dataMap.getDataMap().putAsset("profileImage", asset)
PutDataRequest request = dataMap.asPutDataRequest();
PendingResult<DataApi.DataItemResult> pendingResult = Wearable.DataApi
       .putDataItem(mGoogleApiClient, request);



Receive assets

@Override
public void onDataChanged(DataEventBuffer dataEvents) {
 for (DataEvent event : dataEvents) {
   if (event.getType() == DataEvent.TYPE_CHANGED &&
       event.getDataItem().getUri().getPath().equals("/image")) {
     DataMapItem dataMapItem = DataMapItem.fromDataItem(event.getDataItem());
     Asset profileAsset = dataMapItem.getDataMap().getAsset("profileImage");
     Bitmap bitmap = loadBitmapFromAsset(profileAsset);
     // Do something with the bitmap
   }
 }
}

public Bitmap loadBitmapFromAsset(Asset asset) {
   if (asset == null) {
       throw new IllegalArgumentException("Asset must be non-null");
   }
   ConnectionResult result =
          mGoogleApiClient.blockingConnect(TIMEOUT_MS, TimeUnit.MILLISECONDS);
   if (!result.isSuccess()) {
       return null;
   }
   // convert asset into a file descriptor and block until it's ready
   InputStream assetInputStream = Wearable.DataApi.getFdForAsset(
           mGoogleApiClient, asset).await().getInputStream();
           mGoogleApiClient.disconnect();

   if (assetInputStream == null) {
       Log.w(TAG, "Requested an unknown Asset.");
       return null;
   }
   // decode the stream into a bitmap
   return BitmapFactory.decodeStream(assetInputStream);
}

original source: https://developer.android.com/training/wearables/data-layer/data-items.html

Syncing Data Items

A DataItem defines the data interface that the system uses to synchronize data between handhelds and wearables.

Payload – A byte array, which you can set with whatever data you wish. 크기는  100kb

Path – A unique string that must start with a forward slash

normally don’t implement DataItem directly. Instead, you do the following:

  1. Create a PutDataRequest object, specifying a string path to uniquely identify the item.
  2. Call setData() to set the payload.
  3. If a delay in syncing would negatively impact user experience, call setUrgent().
  4. Call DataApi.putDataItem() to request the system to create the data item.

Sync Data with a Data Map

use the DataMap class. This approach lets you work with data items in the form of an Android Bundle, so the system does object serialization and deserialization for you, and you can manipulate data with key-value pairs.

  1. Create a PutDataMapRequest object, setting the path of the data item.
  2. Call PutDataMapRequest.getDataMap() to obtain a data map that you can set values on.
  3. Set any desired values for the data map using the put...() methods, such as putString().
  4. If a delay in syncing would negatively impact user experience, call setUrgent().
  5. Call PutDataMapRequest.asPutDataRequest() to obtain a PutDataRequest object.
  6. Call DataApi.putDataItem() to request the system to create the data item.

Note: If the handset and wearable devices are disconnected, the data is buffered and synced when the connection is re-established.

ex)

public class MainActivity extends Activity implements
       DataApi.DataListener,
       GoogleApiClient.ConnectionCallbacks,
       GoogleApiClient.OnConnectionFailedListener {

   private static final String COUNT_KEY = "com.example.key.count";

   private GoogleApiClient mGoogleApiClient;
   private int count = 0;

   ...

   // Create a data map and put data in it
   private void increaseCounter() {
       PutDataMapRequest putDataMapReq = PutDataMapRequest.create("/count");
       putDataMapReq.getDataMap().putInt(COUNT_KEY, count++);
       PutDataRequest putDataReq = putDataMapReq.asPutDataRequest();
       PendingResult<DataApi.DataItemResult> pendingResult =
               Wearable.DataApi.putDataItem(mGoogleApiClient, putDataReq);
   }

   ...
}

Set DataItem priority

In Google Play services 8.3 and later, the DataApi interface allows urgent requests for syncing of DataItems.
setUrgent() 을 설정한다.

Listen for Data Item Events

public class MainActivity extends Activity implements
       DataApi.DataListener,
       GoogleApiClient.ConnectionCallbacks,
       GoogleApiClient.OnConnectionFailedListener {

   private static final String COUNT_KEY = "com.example.key.count";

   private GoogleApiClient mGoogleApiClient;
   private int count = 0;

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);

       mGoogleApiClient = new GoogleApiClient.Builder(this)
               .addApi(Wearable.API)
               .addConnectionCallbacks(this)
               .addOnConnectionFailedListener(this)
               .build();
   }

   @Override
   protected void onResume() {
       super.onResume();
       mGoogleApiClient.connect();
   }

   @Override
   public void onConnected(Bundle bundle) {
       Wearable.DataApi.addListener(mGoogleApiClient, this);
   }

   @Override
   protected void onPause() {
       super.onPause();
       Wearable.DataApi.removeListener(mGoogleApiClient, this);
       mGoogleApiClient.disconnect();
   }

   @Override
   public void onDataChanged(DataEventBuffer dataEvents) {
       for (DataEvent event : dataEvents) {
           if (event.getType() == DataEvent.TYPE_CHANGED) {
               // DataItem changed
               DataItem item = event.getDataItem();
               if (item.getUri().getPath().compareTo("/count") == 0) {
                   DataMap dataMap = DataMapItem.fromDataItem(item).getDataMap();
                   updateCount(dataMap.getInt(COUNT_KEY));
               }
           } else if (event.getType() == DataEvent.TYPE_DELETED) {
               // DataItem deleted
           }
       }
   }

   // Our method to update the count
   private void updateCount(int c) { ... }

   ...
}

original source: 

https://developer.android.com/training/wearables/data-layer/accessing.html

Accessing the Wearable Data Layer

To call the Data Layer API, create an instance of GoogleApiClient .

Note: A Wear app can communicate with a phone app using the Data Layer API, but connecting to a network using this API is discouraged.

Here is a minimal GoogleApiClient:

GoogleApiClient mGoogleApiClient = new GoogleApiClient.Builder(this)
       .addConnectionCallbacks(new ConnectionCallbacks() {
               @Override
               public void onConnected(Bundle connectionHint) {
                   Log.d(TAG, "onConnected: " + connectionHint);
                   // Now you can use the Data Layer API
               }
               @Override
               public void onConnectionSuspended(int cause) {
                   Log.d(TAG, "onConnectionSuspended: " + cause);
               }
       })
       .addOnConnectionFailedListener(new OnConnectionFailedListener() {
               @Override
               public void onConnectionFailed(ConnectionResult result) {
                   Log.d(TAG, "onConnectionFailed: " + result);
               }
           })
       // Request access only to the Wearable API
       .addApi(Wearable.API)
       .build();

Important: If you are adding multiple APIs to a GoogleApiClient, you may run into client connection errors on devices that do not have the Android Wear app installed. To avoid connection errors, call the addApiIfAvailable() method and pass in the Wearable API to indicate that your client should gracefully handle the missing API. For more information, see Access the Wearable API.

Before you use the data layer API, start a connection on your client by calling the connect() method, as described in Start a Connection. When the system invokes the onConnected() callback for your client, you’re ready to use the Data Layer API.

original source : https://developer.android.com/training/wearables/data-layer/network-access.html

Network Access and Syncing

With Android Wear 2.0, a watch can communicate with a network directly.

This direct network access replaces the use (in Wear 1.x) of the Data Layer API for connecting to a network .

Network Access

watch’s network traffic generally is proxied through the phone. But when a phone is unavailable, Wi-Fi and cellular networks are used, depending on the hardware. The Wear platform handles transitions between networks.

You can use protocols such as HTTP, TCP, and UDP. However, the android.webkit APIs (including the CookieManager class) are not available. You can use cookies by reading and writing headers on requests and responses.

참고사항

  • The JobScheduler API for asynchronous jobs, including polling at regular intervals (described below)
  • Multi-networking APIs if you need to connect to specific network types; see Multiple Network Connections

High-bandwidth Network Access

The Android Wear platform manages network connectivity.

The platform chooses the default, active network by balancing two factors:

  • The need for long battery life
  • The need for network bandwidth

            Acquiring a High-Bandwidth Network

high-bandwidth network가 항상 사용가능한게 아니므로

high-bandwidth network 가 필요한 경우아래의 과정을 거쳐야 한다. 

  1. Check for an active network, and if there is one, check its bandwidth.
  2. If there isn’t an active network, or its bandwidth is insufficient, request access to an unmetered Wi-Fi or cellular network.

ConnectivityManager class 를 사용하여 네트워크가 있는지 밴드크기 어떤지 확인가능하다. 

int MIN_BANDWIDTH_KBPS = 320;
mConnectivityManager =
 (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
Network activeNetwork = mConnectivityManager.getActiveNetwork();

if (activeNetwork != null) {
 int bandwidth =
   mConnectivityManager.getNetworkCapabilities(activeNetwork).getLinkDownstreamBandwidthKbps();

 if (bandwidth < MIN_BANDWIDTH_KBPS) {
   // Request a high-bandwidth network
 }
} else {
 // You already are on a high-bandwidth network, so start your network request
}

You can request an unmetered, high-bandwidth network using the ConnectivityManager.

When the network is ready (e.g., the device’s Wi-Fi radio connects to a saved network), the onAvailable() method of your NetworkCallback instance is called. If a suitable network is not found, the onAvailable() method is not called. Therefore, you should time-out your request manually; see Waiting for Network Availability.

mNetworkCallback = new ConnectivityManager.NetworkCallback() {
 @Override
 public void onAvailable(Network network) {
   if (bindProcessToNetwork(network)) {
     // socket connections will now use this network
   } else {
     // app doesn't have android.permission.INTERNET permission
   }
 }
};

NetworkRequest request = new NetworkRequest.Builder()
 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
 .build();

mConnectivityManager.requestNetwork(request, mNetworkCallback);

       Releasing the Network

사용이 끝난 network는 release해야 한다. 

mConnectivityManager.bindProcessToNetwork(null);
mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);

또 이작업은 

activity’s onStop() method 안에서도 실행되어야 한다.

       Waiting for Network Availability

if a watch cannot connect to a network, the onAvailable() method of your NetworkCallback instance is not called. Therefore, you should time-out the request after a predetermined length of time and release any associated resources.

int MESSAGE_CONNECTIVITY_TIMEOUT = 1;
long NETWORK_CONNECTIVITY_TIMEOUT_MS = 10000

mHandler = new Handler() {
 @Override
 public void handleMessage(Message msg) {
   switch (msg.what) {
     case MESSAGE_CONNECTIVITY_TIMEOUT:
       // unregister the network
       break;
   }
 }
};

mNetworkCallback = new ConnectivityManager.NetworkCallback() {
 @Override
 public void onAvailable(Network network) {
   mHandler.removeMessages(MESSAGE_CONNECTIVITY_TIMEOUT);
   ...
 }
};

mConnectivityManager.requestNetwork(request, mNetworkCallback);

mHandler.sendMessageDelayed(
 mHandler.obtainMessage(MESSAGE_CONNECTIVITY_TIMEOUT),
 NETWORK_CONNECTIVITY_TIMEOUT_MS);

       

Monitoring the Network State

mNetworkCallback = ConnectivityManager.NetworkCallback {
 @Override
 public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
   int bandwidth =
     mConnectivityManager.getNetworkCapabilities(network).getLinkDownstreamBandwidthKbps();

     if (bandwidth < MIN_BANDWIDTH.KBPS) {
       // handle insufficient network bandwidth
     }
 }

 @Override
 public void onLost(Network network) {
   // handle network loss
 }
}

       

Launching the Wi-Fi Settings Activity

사용자가 저장한 wi-fi가 없거나 접근 불가능한 경우 사용자를 wi-fi 설정 activity로 이동시킬수 있다.

context.startActivity(new Intent("com.google.android.clockwork.settings.connectivity.wifi.ADD_NETWORK_SETTINGS"));

Cloud Messaging

For sending notifications, apps can directly use Firebase Cloud Messaging (FCM)

By default, notifications are bridged (shared) from a phone app to a watch. If you have a standalone Wear app and a corresponding phone app, duplicate notifications can occur.

Using Background Services

You should schedule jobs with the JobScheduler API, which enables your app to register for Doze-safe code execution.

Jobs should use a JobInfo.Builder object to provide constraints and metadata.

  • To schedule a task that requires networking, use setRequiredNetworkType(int networkType), specifying NETWORK_TYPE_ANY or NETWORK_TYPE_UNMETERED; note that NETWORK_TYPE_UNMETERED is for large data transfers while NETWORK_TYPE_ANY is for small transfers
  • To schedule a task while charging, use setRequiresCharging(boolean requiresCharging)
  • To specify that a device is idle for a task, use setRequiresDeviceIdle(boolean requiresDeviceIdle); this method is useful for lower-priority background work or synchronization, especially when used with setRequiresCharging

Note that some low-bandwidth networks, such as Bluetooth LE, are considered meter

Scheduling with constraints

You can use the builder method setExtras to attach a bundle of app-specific metadata to the job request. When your job executes, this bundle is provided to your job service. Note the MY_JOB_ID value passed to the JobInfo.Builder constructor. This MY_JOB_ID value is an app-provided identifier. Subsequent calls to cancel, and subsequent jobs created with that same value, will update the existing job.

JobInfo jobInfo = new JobInfo.Builder(MY_JOB_ID,
       new ComponentName(this, MyJobService.class))
       .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
       .setRequiresCharging(true)
       .setExtras(extras)
       .build();
((JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE))
       .schedule(jobInfo);

아래는 위의 작업을 서비스하는 부분설명.

JobParameters object is passed into the onStartJobmethod. The JobParameters object enables you to get the job ID value along with any extras bundle provided when scheduling the job.

The onStartJobmethod is called on the main application thread, and therefore any expensive logic should be run from a separate thread .

When work is complete, you would call the jobFinished method to notify JobScheduler that the task is done.

public class MyJobService extends JobService {
   @Override public boolean onStartJob(JobParameters params) {
       new JobAsyncTask().execute(params);
       return true;
   }

   private class JobAsyncTask extends AsyncTask