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/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/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

original source : https://developer.android.com/training/wearables/apps/packaging.html

wear 1.0 의 경우 독립된 wearable app이 만들어 질수 없다. 반드시 mobile app이 같이 만들어져야 한다. wear 2.0 부터 완전히 독립된 app을 만들수 있다. 이경우 target api가 25이상이 되어야 한다. 1.0과 2.0을 같이 지원하는 app을 만드는 경우 target api는 23 이상이 되어야 한다. wearable app, mobile app 둘다 만드는 경우 같은 sign key를 사용해야한다. (1.0에서도 이는 똑같이 적용된다.) 

Planning for the Play Store

app을 google play에 올리는 경우. wearable app만 배포하는 경우는 평상시 보통 mobile app 배포하는 것과 같은 과정으로 하면된다. 단 wearable, mobile app을 둘다 배포하는 경우는 

Multi-APK delivery method 방법을 따라야 한다.

Distribution to Wear 2.0 watches

사용자가 만약 wear 2.0를 사용하며  mobile에서 연결된 wearable app을 가지고 있는 app을 설치하는 경우 watch app이 mobile app embedded 되었거나 따로 play console을 통해 배포된경우든 상관없이 mobile app을 설치할때 wearable app을 설치할수 있게 notification이 나오게 된다.

  • When you update a phone APK with a new embedded watch APK, the user’s watch APK automatically is updated.
  • When you upload a watch APK via the Play Console, you can update your Wear APK independently from the phone APK, and users receive updates via the watch Play Store.
  • For an embedded watch APK, the user’s watch APK is automatically updated when the APK on the phone is updated. In the case of multi-APK, the update behavior depends on the Play Store setting (Auto-update apps). It is strongly discouraged to have both an embedded APK and multi-APK for your app if they are not the same version.

Distribution to Wear 1.x and 2.0 watches

original source 링크 내용 참조

Specifying a version code

버전 코드 작성요령

  • Set the first two digits of the version code to the targetSdkVersion, e.g. 25
  • Set the next three digits to the product version, e.g. 152 for a product version of 1.5.2
  • Set the next two digits to build or release number, e.g. 01
  • Reserve the last two digits for a multi-APK variant, e.g. 00

Support in the Gradle file

wear 1.0 과 2.0을 둘다 이용하는 경우 product flavors 를 gradle 화일에 적용하는 것을 검토하라 . original source 링크를 참조할것

Migrating a Wear 1.0 APK from Embedded to Multi-APK

original source 링크를 참조할것

Setting Up Targeting for a Watch

In your Android manifest file, you must set the uses-feature element to android.hardware.type.watch. Additionally, do not set the required attribute to false. A single APK for Wear and non-Wear devices presently is not supported.

<manifest package="com.example.standalone"
   xmlns:android="http://schemas.android.com/apk/res/android">
   <uses-feature
       android:name="android.hardware.type.watch"/>
   ...
</manifest>

Specifying an App as Standalone

Wear 2.0 requires a meta-data element in the Android manifest file of watch apps, as a child of the <application> element. The name of the meta-data element is com.google.android.wearable.standalone and the value must be true or false.

If not all of your APKs (alpha, beta, and production) that currently are served to users have the above setting, your app will be unavailable when a user searches on a watch paired to an iPhone.

<application>
...
 <meta-data
   android:name="com.google.android.wearable.standalone"
   android:value="true" />
...
</application>

Note: Even if the value is false, the watch app can be installed before the phone app is installed.

If your Wear 2.0 app has an accompanying phone app, use the same package name for your Wear app and that phone app

Using the Play Console

     Uploading and publishing your APK

Embedding a Wear 1.x APK

     

Packaging a Wear 1.x app with Android Studio

      Signing the Wear 1.x app and phone app separately

     

Package a Wear 1.x app manually



Turning off asset compression

ensure that the watch app is not doubly compressed 

original source: https://developer.android.com/training/wearables/apps/always-on.html

Keeping Your App Visible

Android Wear devices running Android version 5.1 or higher allow apps to remain in the foreground while saving battery power.

Important: The 27.0.0 version of the Android Support Library enables a new way to support ambient mode that uses the AmbientMode(새로운방버) class rather than the WearableActivity(기존의방법) class. You can decide whether you want to use

Configure your project

기본적으로 ambient상태에서도 app이 작동하게 하기 위해서는 기본적으로 설정작업이 필요하다.

  1. Create or update your project based on the configurations on the Creating and Running a Wearable App page.
  2. Add the WAKE_LOCK permission to the Android Manifest file:
<uses-permission android:name="android.permission.WAKE_LOCK" />

Ambient Mode Using the AmbientMode Class

AmbientMode 클래스 사용의 장점

Note: The AmbientMode.attachAmbientSupport() method attaches a headless fragment to the Activity class or subclass that you provide, and subsequent calls to FragmentManager.getFragments() return a reference to this fragment (which is not intended to be used in any way)

AmbientMode class를 이용한 방법

1. Create a subclass of one of the Activity classes.

2. 

public class MainActivity extends Activity implements AmbientMode.AmbientCallbackProvider {
   …
   @Override
   public AmbientMode.AmbientCallback getAmbientCallback() {
       return new MyAmbientCallback();
   }
   …
}

3. 

This method returns anAmbientMode.AmbientController. The controller allows you to check the ambient state outside of the callbacks

public class MainActivity extends Activity implements AmbientMode.AmbientCallbackProvider {
   …
   /*
    * Declare an ambient mode controller, which will be used by
    * the activity to determine if the current mode is ambient.
    */
   private AmbientMode.AmbientController mAmbientController;
   …
   @Override
   public void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
   ...
       mAmbientController = AmbientMode.attachAmbientSupport(this);
   }
   ...
}

4.

Create an inner class that extends the AmbientCallback class in order to act on ambient events

private class MyAmbientCallback extends AmbientMode.AmbientCallback {
   @Override
   public void onEnterAmbient(Bundle ambientDetails) {
            // Handle entering ambient mode
   }

   @Override
   public void onExitAmbient() {
     // Handle exiting ambient mode
    }

   @Override
   public void onUpdateAmbient() {
     // Update the content
   }
}

Ambient Mode Using the WearableActivity Class

       Create an activity that supports ambient mode

1.Create an activity that extends WearableActivity.

2. In the onCreate() method of your activity, call the setAmbientEnabled() method.

public class MainActivity extends WearableActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);

   setAmbientEnabled();
   ...
}

       Handle transitions between modes

When the activity switches to ambient mode, the system calls the onEnterAmbient() method in your wearable activity.

예) 

@Override
public void onEnterAmbient(Bundle ambientDetails) {
   super.onEnterAmbient(ambientDetails);

   mStateTextView.setTextColor(Color.WHITE);
   mStateTextView.getPaint().setAntiAlias(false);
}

the activity switches from ambient mode to interactive mode.

예)

@Override
public void onExitAmbient() {
   super.onExitAmbient();

   mStateTextView.setTextColor(Color.GREEN);
   mStateTextView.getPaint().setAntiAlias(true);
}

       Update Content in Ambient Mode

You should strongly consider only overriding the onUpdateAmbient() method to update the screen once a minute in ambient mode. If your app requires more frequent updates, take into consideration that there is a trade-off between battery life and the frequency of updates. To realize battery savings, updates should be no more than once every 10 seconds.

                  Update once a minute (일분에 한번만 업데이트하는 경우)

onUpdateAmbient(), that allows you to update the screen

예)

@Override
public void onUpdateAmbient() {
   super.onUpdateAmbient();
   // Update the content
}

                 

Update more frequently (좀더 자주 업데이트해야 하는 경우)

use an AlarmManager object to wake the processor

사용방법

  1. Prepare the alarm manager.
  2. Set the frequency of the updates.
  3. Schedule the next update when the activity switches to ambient mode or is currently in ambient mode.
  4. Cancel the alarm when the activity switches to interactive mode or the activity is stopped

Note: The alarm manager may create new instances of your activity as they are triggered. To prevent this situation, ensure that your activity is declared with the android:launchMode="singleInstance" parameter in the manifest.

                            1. Prepare the alarm manager

// Action for updating the display in ambient mode, per our custom refresh cycle.
private static final String AMBIENT_UPDATE_ACTION = "com.your.package.action.AMBIENT_UPDATE";

private AlarmManager mAmbientUpdateAlarmManager;
private PendingIntent mAmbientUpdatePendingIntent;
private BroadcastReceiver mAmbientUpdateBroadcastReceiver;

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

   setAmbientEnabled();

   mAmbientUpdateAlarmManager =
       (AlarmManager) getSystemService(Context.ALARM_SERVICE);

   Intent ambientUpdateIntent = new Intent(AMBIENT_UPDATE_ACTION);

   mAmbientUpdatePendingIntent = PendingIntent.getBroadcast(
       this, 0, ambientUpdateIntent, PendingIntent.FLAG_UPDATE_CURRENT);

   mAmbientUpdateBroadcastReceiver = new BroadcastReceiver() {
       @Override
       public void onReceive(Context context, Intent intent) {
           refreshDisplayAndSetNextUpdate();
       }
   };
   ...
}
@Override
public void onResume() {
   super.onResume();
   IntentFilter filter = new IntentFilter(AMBIENT_UPDATE_ACTION);
   registerReceiver(mAmbientUpdateBroadcastReceiver, filter);
       ...
}

@Override
public void onPause() {
   super.onPause();
   unregisterReceiver(mAmbientUpdateBroadcastReceiver);
   mAmbientUpdateAlarmManager.cancel(mAmbientUpdatePendingIntent);
   ...
}

                             2.

Update screen and schedule data updates

// Milliseconds between waking processor/screen for updates
private static final long AMBIENT_INTERVAL_MS = TimeUnit.SECONDS.toMillis(20);
private void refreshDisplayAndSetNextUpdate() {
   if (isAmbient()) {
       // Implement data retrieval and update the screen for ambient mode
   } else {
       // Implement data retrieval and update the screen for interactive mode
   }
   long timeMs = System.currentTimeMillis();
   // Schedule a new alarm
   if (isAmbient()) {
       // Calculate the next trigger time
       long delayMs = AMBIENT_INTERVAL_MS - (timeMs % AMBIENT_INTERVAL_MS);
       long triggerTimeMs = timeMs + delayMs;
       mAmbientStateAlarmManager.setExact(
           AlarmManager.RTC_WAKEUP,
           triggerTimeMs,
           mAmbientStatePendingIntent);
   } else {
       // Calculate the next trigger time for interactive mode
   }
}

                               3.

Schedule the next alarm

@Override
public void onEnterAmbient(Bundle ambientDetails) {
   super.onEnterAmbient(ambientDetails);
   refreshDisplayAndSetNextUpdate();
}

@Override
public void onUpdateAmbient() {
   super.onUpdateAmbient();
   refreshDisplayAndSetNextUpdate();
}

                                 4.

Cancel the alarm

@Override
public void onExitAmbient() {
   super.onExitAmbient();
   mAmbientStateAlarmManager.cancel(mAmbientUpdatePendingIntent);
}
@Override
public void onDestroy() {
   mAmbientStateAlarmManager.cancel(mAmbientUpdatePendingIntent);
   super.onDestroy();
}

Maintain Backward-compatibility

Android versions prior to 5.1 에서는 위의 방법이 적용되지 않으며 보통 activity처럼 행동한다.

ambient mode 모드가 되는 순간 작동하는 순간 activity는 exit하게 되고 home screen 으로 이동한다.

Android versions prior to 5.1 을 개발하는 앱이 지원하지 않는 경우 

<uses-library android:name="com.google.android.wearable" android:required="true" />

를 manifest에 작성해서 설치를 막는다.