blob: 0077a5bdcdce94f22e37a5610103b8f19092d0da [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* 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 android.support.wear.ambient;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.CallSuper;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
import com.google.android.wearable.compat.WearableActivityController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
/**
* Use this as a headless Fragment to add ambient support to an Activity on Wearable devices.
* <p>
* The application that uses this should add the {@link android.Manifest.permission#WAKE_LOCK}
* permission to its manifest.
* <p>
* The primary entry point for this code is the {@link #attachAmbientSupport(Activity)} method.
* It should be called with an {@link Activity} as an argument and that {@link Activity} will then
* be able to receive ambient lifecycle events through an {@link AmbientCallback}. The
* {@link Activity} will also receive a {@link AmbientController} object from the attachment which
* can be used to query the current status of the ambient mode.
* An example of how to attach {@link AmbientMode} to your {@link Activity} and use
* the {@link AmbientController} can be found below:
* <p>
* <pre class="prettyprint">{@code
* AmbientMode.AmbientController controller = AmbientMode.attachAmbientSupport(this);
* boolean isAmbient = controller.isAmbient();
* }</pre>
*/
public final class AmbientMode extends Fragment {
private static final String TAG = "AmbientMode";
/**
* Property in bundle passed to {@code AmbientCallback#onEnterAmbient(Bundle)} to indicate
* whether burn-in protection is required. When this property is set to true, views must be
* shifted around periodically in ambient mode. To ensure that content isn't shifted off
* the screen, avoid placing content within 10 pixels of the edge of the screen. Activities
* should also avoid solid white areas to prevent pixel burn-in. Both of these requirements
* only apply in ambient mode, and only when this property is set to true.
*/
public static final String EXTRA_BURN_IN_PROTECTION =
WearableActivityController.EXTRA_BURN_IN_PROTECTION;
/**
* Property in bundle passed to {@code AmbientCallback#onEnterAmbient(Bundle)} to indicate
* whether the device has low-bit ambient mode. When this property is set to true, the screen
* supports fewer bits for each color in ambient mode. In this case, activities should disable
* anti-aliasing in ambient mode.
*/
public static final String EXTRA_LOWBIT_AMBIENT =
WearableActivityController.EXTRA_LOWBIT_AMBIENT;
/**
* Fragment tag used by default when adding {@link AmbientMode} to add ambient support to an
* {@link Activity}.
*/
public static final String FRAGMENT_TAG = "android.support.wearable.ambient.AmbientMode";
/**
* Interface for any {@link Activity} that wishes to implement Ambient Mode. Use the
* {@link #getAmbientCallback()} method to return and {@link AmbientCallback} which can be used
* to bind the {@link AmbientMode} to the instantiation of this interface.
* <p>
* <pre class="prettyprint">{@code
* return new AmbientMode.AmbientCallback() {
* public void onEnterAmbient(Bundle ambientDetails) {...}
* public void onExitAmbient(Bundle ambientDetails) {...}
* }
* }</pre>
*/
public interface AmbientCallbackProvider {
/**
* @return the {@link AmbientCallback} to be used by this class to communicate with the
* entity interested in ambient events.
*/
AmbientCallback getAmbientCallback();
}
/**
* Callback to receive ambient mode state changes. It must be used by all users of AmbientMode.
*/
public abstract static class AmbientCallback {
/**
* Called when an activity is entering ambient mode. This event is sent while an activity is
* running (after onResume, before onPause). All drawing should complete by the conclusion
* of this method. Note that {@code invalidate()} calls will be executed before resuming
* lower-power mode.
*
* @param ambientDetails bundle containing information about the display being used.
* It includes information about low-bit color and burn-in protection.
*/
public void onEnterAmbient(Bundle ambientDetails) {}
/**
* Called when the system is updating the display for ambient mode. Activities may use this
* opportunity to update or invalidate views.
*/
public void onUpdateAmbient() {}
/**
* Called when an activity should exit ambient mode. This event is sent while an activity is
* running (after onResume, before onPause).
*/
public void onExitAmbient() {}
}
private final AmbientDelegate.AmbientCallback mCallback =
new AmbientDelegate.AmbientCallback() {
@Override
public void onEnterAmbient(Bundle ambientDetails) {
if (mSuppliedCallback != null) {
mSuppliedCallback.onEnterAmbient(ambientDetails);
}
}
@Override
public void onExitAmbient() {
if (mSuppliedCallback != null) {
mSuppliedCallback.onExitAmbient();
}
}
@Override
public void onUpdateAmbient() {
if (mSuppliedCallback != null) {
mSuppliedCallback.onUpdateAmbient();
}
}
};
private AmbientDelegate mDelegate;
@Nullable
private AmbientCallback mSuppliedCallback;
private AmbientController mController;
/**
* Constructor
*/
public AmbientMode() {
mController = new AmbientController();
}
@Override
@CallSuper
public void onAttach(Context context) {
super.onAttach(context);
mDelegate = new AmbientDelegate(getActivity(), new WearableControllerProvider(), mCallback);
if (context instanceof AmbientCallbackProvider) {
mSuppliedCallback = ((AmbientCallbackProvider) context).getAmbientCallback();
} else {
Log.w(TAG, "No callback provided - enabling only smart resume");
}
}
@Override
@CallSuper
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDelegate.onCreate();
if (mSuppliedCallback != null) {
mDelegate.setAmbientEnabled();
}
}
@Override
@CallSuper
public void onResume() {
super.onResume();
mDelegate.onResume();
}
@Override
@CallSuper
public void onPause() {
mDelegate.onPause();
super.onPause();
}
@Override
@CallSuper
public void onStop() {
mDelegate.onStop();
super.onStop();
}
@Override
@CallSuper
public void onDestroy() {
mDelegate.onDestroy();
super.onDestroy();
}
@Override
@CallSuper
public void onDetach() {
mDelegate = null;
super.onDetach();
}
/**
* Attach ambient support to the given activity. Calling this method with an Activity
* implementing the {@link AmbientCallbackProvider} interface will provide you with an
* opportunity to react to ambient events such as {@code onEnterAmbient}. Alternatively,
* you can call this method with an Activity which does not implement
* the {@link AmbientCallbackProvider} interface and that will only enable the auto-resume
* functionality. This is equivalent to providing (@code null} from
* the {@link AmbientCallbackProvider}.
*
* @param activity the activity to attach ambient support to.
* @return the associated {@link AmbientController} which can be used to query the state of
* ambient mode.
*/
public static <T extends Activity> AmbientController attachAmbientSupport(T activity) {
FragmentManager fragmentManager = activity.getFragmentManager();
AmbientMode ambientFragment = (AmbientMode) fragmentManager.findFragmentByTag(FRAGMENT_TAG);
if (ambientFragment == null) {
AmbientMode fragment = new AmbientMode();
fragmentManager
.beginTransaction()
.add(fragment, FRAGMENT_TAG)
.commit();
ambientFragment = fragment;
}
return ambientFragment.mController;
}
@Override
public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
if (mDelegate != null) {
mDelegate.dump(prefix, fd, writer, args);
}
}
@VisibleForTesting
void setAmbientDelegate(AmbientDelegate delegate) {
mDelegate = delegate;
}
/**
* A class for interacting with the ambient mode on a wearable device. This class can be used to
* query the current state of ambient mode. An instance of this class is returned to the user
* when they attach their {@link Activity} to {@link AmbientMode}.
*/
public final class AmbientController {
private static final String TAG = "AmbientController";
// Do not initialize outside of this class.
AmbientController() {}
/**
* @return {@code true} if the activity is currently in ambient.
*/
public boolean isAmbient() {
return mDelegate == null ? false : mDelegate.isAmbient();
}
}
}