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 */ 16package androidx.wear.ambient; 17 18import android.app.Activity; 19import android.content.Context; 20import android.os.Bundle; 21import android.util.Log; 22 23import androidx.annotation.CallSuper; 24import androidx.annotation.Nullable; 25import androidx.annotation.VisibleForTesting; 26import androidx.fragment.app.Fragment; 27import androidx.fragment.app.FragmentActivity; 28import androidx.fragment.app.FragmentManager; 29 30import com.google.android.wearable.compat.WearableActivityController; 31 32import java.io.FileDescriptor; 33import 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 */ 54public 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