Home | History | Annotate | Download | only in ambient
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package androidx.wear.ambient;
     17 
     18 import android.app.Activity;
     19 import android.content.Context;
     20 import android.os.Bundle;
     21 import android.util.Log;
     22 
     23 import androidx.annotation.CallSuper;
     24 import androidx.annotation.Nullable;
     25 import androidx.annotation.VisibleForTesting;
     26 import androidx.fragment.app.Fragment;
     27 import androidx.fragment.app.FragmentActivity;
     28 import androidx.fragment.app.FragmentManager;
     29 
     30 import com.google.android.wearable.compat.WearableActivityController;
     31 
     32 import java.io.FileDescriptor;
     33 import java.io.PrintWriter;
     34 
     35 /**
     36  * Use this as a headless Fragment to add ambient support to an Activity on Wearable devices.
     37  * <p>
     38  * The application that uses this should add the {@link android.Manifest.permission#WAKE_LOCK}
     39  * permission to its manifest.
     40  * <p>
     41  * The primary entry  point for this code is the {@link #attach(FragmentActivity)} method.
     42  * It should be called with an {@link FragmentActivity} as an argument and that
     43  * {@link FragmentActivity} will then be able to receive ambient lifecycle events through
     44  * an {@link AmbientCallback}. The {@link FragmentActivity} will also receive a
     45  * {@link AmbientController} object from the attachment which can be used to query the current
     46  * status of the ambient mode. An example of how to attach {@link AmbientModeSupport} to your
     47  * {@link FragmentActivity} and use the {@link AmbientController} can be found below:
     48  * <p>
     49  * <pre class="prettyprint">{@code
     50  *     AmbientMode.AmbientController controller = AmbientMode.attachAmbientSupport(this);
     51  *     boolean isAmbient =  controller.isAmbient();
     52  * }</pre>
     53  */
     54 public final class AmbientModeSupport extends Fragment {
     55     private static final String TAG = "AmbientMode";
     56 
     57     /**
     58      * Property in bundle passed to {@code AmbientCallback#onEnterAmbient(Bundle)} to indicate
     59      * whether burn-in protection is required. When this property is set to true, views must be
     60      * shifted around periodically in ambient mode. To ensure that content isn't shifted off
     61      * the screen, avoid placing content within 10 pixels of the edge of the screen. Activities
     62      * should also avoid solid white areas to prevent pixel burn-in. Both of these requirements
     63      * only apply in ambient mode, and only when this property is set to true.
     64      */
     65     public static final String EXTRA_BURN_IN_PROTECTION =
     66             WearableActivityController.EXTRA_BURN_IN_PROTECTION;
     67 
     68     /**
     69      * Property in bundle passed to {@code AmbientCallback#onEnterAmbient(Bundle)} to indicate
     70      * whether the device has low-bit ambient mode. When this property is set to true, the screen
     71      * supports fewer bits for each color in ambient mode. In this case, activities should disable
     72      * anti-aliasing in ambient mode.
     73      */
     74     public static final String EXTRA_LOWBIT_AMBIENT =
     75             WearableActivityController.EXTRA_LOWBIT_AMBIENT;
     76 
     77     /**
     78      * Fragment tag used by default when adding {@link AmbientModeSupport} to add ambient support to
     79      * a {@link FragmentActivity}.
     80      */
     81     public static final String FRAGMENT_TAG = "android.support.wearable.ambient.AmbientMode";
     82 
     83     /**
     84      * Interface for any {@link Activity} that wishes to implement Ambient Mode. Use the
     85      * {@link #getAmbientCallback()} method to return and {@link AmbientCallback} which can be used
     86      * to bind the {@link AmbientModeSupport} to the instantiation of this interface.
     87      * <p>
     88      * <pre class="prettyprint">{@code
     89      * return new AmbientMode.AmbientCallback() {
     90      *     public void onEnterAmbient(Bundle ambientDetails) {...}
     91      *     public void onExitAmbient(Bundle ambientDetails) {...}
     92      *  }
     93      * }</pre>
     94      */
     95     public interface AmbientCallbackProvider {
     96         /**
     97          * @return the {@link AmbientCallback} to be used by this class to communicate with the
     98          * entity interested in ambient events.
     99          */
    100         AmbientCallback getAmbientCallback();
    101     }
    102 
    103     /**
    104      * Callback to receive ambient mode state changes. It must be used by all users of AmbientMode.
    105      */
    106     public abstract static class AmbientCallback {
    107         /**
    108          * Called when an activity is entering ambient mode. This event is sent while an activity is
    109          * running (after onResume, before onPause). All drawing should complete by the conclusion
    110          * of this method. Note that {@code invalidate()} calls will be executed before resuming
    111          * lower-power mode.
    112          *
    113          * @param ambientDetails bundle containing information about the display being used.
    114          *                      It includes information about low-bit color and burn-in protection.
    115          */
    116         public void onEnterAmbient(Bundle ambientDetails) {}
    117 
    118         /**
    119          * Called when the system is updating the display for ambient mode. Activities may use this
    120          * opportunity to update or invalidate views.
    121          */
    122         public void onUpdateAmbient() {}
    123 
    124         /**
    125          * Called when an activity should exit ambient mode. This event is sent while an activity is
    126          * running (after onResume, before onPause).
    127          */
    128         public void onExitAmbient() {}
    129     }
    130 
    131     private final AmbientDelegate.AmbientCallback mCallback =
    132             new AmbientDelegate.AmbientCallback() {
    133                 @Override
    134                 public void onEnterAmbient(Bundle ambientDetails) {
    135                     if (mSuppliedCallback != null) {
    136                         mSuppliedCallback.onEnterAmbient(ambientDetails);
    137                     }
    138                 }
    139 
    140                 @Override
    141                 public void onExitAmbient() {
    142                     if (mSuppliedCallback != null) {
    143                         mSuppliedCallback.onExitAmbient();
    144                     }
    145                 }
    146 
    147                 @Override
    148                 public void onUpdateAmbient() {
    149                     if (mSuppliedCallback != null) {
    150                         mSuppliedCallback.onUpdateAmbient();
    151                     }
    152                 }
    153             };
    154     private AmbientDelegate mDelegate;
    155     @Nullable
    156     private AmbientCallback mSuppliedCallback;
    157     private AmbientController mController;
    158 
    159     /**
    160      * Constructor
    161      */
    162     public AmbientModeSupport() {
    163         mController = new AmbientController();
    164     }
    165 
    166     @Override
    167     @CallSuper
    168     public void onAttach(Context context) {
    169         super.onAttach(context);
    170         mDelegate = new AmbientDelegate(getActivity(), new WearableControllerProvider(), mCallback);
    171 
    172         if (context instanceof AmbientCallbackProvider) {
    173             mSuppliedCallback = ((AmbientCallbackProvider) context).getAmbientCallback();
    174         } else {
    175             Log.w(TAG, "No callback provided - enabling only smart resume");
    176         }
    177     }
    178 
    179     @Override
    180     @CallSuper
    181     public void onCreate(Bundle savedInstanceState) {
    182         super.onCreate(savedInstanceState);
    183         mDelegate.onCreate();
    184         if (mSuppliedCallback != null) {
    185             mDelegate.setAmbientEnabled();
    186         }
    187     }
    188 
    189     @Override
    190     @CallSuper
    191     public void onResume() {
    192         super.onResume();
    193         mDelegate.onResume();
    194     }
    195 
    196     @Override
    197     @CallSuper
    198     public void onPause() {
    199         mDelegate.onPause();
    200         super.onPause();
    201     }
    202 
    203     @Override
    204     @CallSuper
    205     public void onStop() {
    206         mDelegate.onStop();
    207         super.onStop();
    208     }
    209 
    210     @Override
    211     @CallSuper
    212     public void onDestroy() {
    213         mDelegate.onDestroy();
    214         super.onDestroy();
    215     }
    216 
    217     @Override
    218     @CallSuper
    219     public void onDetach() {
    220         mDelegate = null;
    221         super.onDetach();
    222     }
    223 
    224     /**
    225      * Attach ambient support to the given activity. Calling this method with an Activity
    226      * implementing the {@link AmbientCallbackProvider} interface will provide you with an
    227      * opportunity to react to ambient events such as {@code onEnterAmbient}. Alternatively,
    228      * you can call this method with an Activity which does not implement
    229      * the {@link AmbientCallbackProvider} interface and that will only enable the auto-resume
    230      * functionality. This is equivalent to providing (@code null} from
    231      * the {@link AmbientCallbackProvider}.
    232      *
    233      * @param activity the activity to attach ambient support to.
    234      * @return the associated {@link AmbientController} which can be used to query the state of
    235      * ambient mode.
    236      */
    237     public static <T extends FragmentActivity> AmbientController attach(T activity) {
    238         FragmentManager fragmentManager = activity.getSupportFragmentManager();
    239         AmbientModeSupport ambientFragment =
    240                 (AmbientModeSupport) fragmentManager.findFragmentByTag(FRAGMENT_TAG);
    241         if (ambientFragment == null) {
    242             AmbientModeSupport fragment = new AmbientModeSupport();
    243             fragmentManager
    244                     .beginTransaction()
    245                     .add(fragment, FRAGMENT_TAG)
    246                     .commit();
    247             ambientFragment = fragment;
    248         }
    249         return ambientFragment.mController;
    250     }
    251 
    252     @Override
    253     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
    254         if (mDelegate != null) {
    255             mDelegate.dump(prefix, fd, writer, args);
    256         }
    257     }
    258 
    259     @VisibleForTesting
    260     void setAmbientDelegate(AmbientDelegate delegate) {
    261         mDelegate = delegate;
    262     }
    263 
    264     /**
    265      * A class for interacting with the ambient mode on a wearable device. This class can be used to
    266      * query the current state of ambient mode. An instance of this class is returned to the user
    267      * when they attach their {@link Activity} to {@link AmbientModeSupport}.
    268      */
    269     public final class AmbientController {
    270         private static final String TAG = "AmbientController";
    271 
    272         // Do not initialize outside of this class.
    273         AmbientController() {}
    274 
    275         /**
    276          * @return {@code true} if the activity is currently in ambient.
    277          */
    278         public boolean isAmbient() {
    279             return mDelegate == null ? false : mDelegate.isAmbient();
    280         }
    281     }
    282 }
    283