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.app.Fragment; 20import android.app.FragmentManager; 21import android.content.Context; 22import android.os.Bundle; 23import android.util.Log; 24 25import androidx.annotation.CallSuper; 26import androidx.annotation.Nullable; 27import androidx.annotation.VisibleForTesting; 28 29import com.google.android.wearable.compat.WearableActivityController; 30 31import java.io.FileDescriptor; 32import java.io.PrintWriter; 33 34/** 35 * Use this as a headless Fragment to add ambient support to an Activity on Wearable devices. 36 * <p> 37 * The application that uses this should add the {@link android.Manifest.permission#WAKE_LOCK} 38 * permission to its manifest. 39 * <p> 40 * The primary entry point for this code is the {@link #attachAmbientSupport(Activity)} method. 41 * It should be called with an {@link Activity} as an argument and that {@link Activity} will then 42 * be able to receive ambient lifecycle events through an {@link AmbientCallback}. The 43 * {@link Activity} will also receive a {@link AmbientController} object from the attachment which 44 * can be used to query the current status of the ambient mode. 45 * An example of how to attach {@link AmbientMode} to your {@link Activity} and use 46 * the {@link AmbientController} can be found below: 47 * <p> 48 * <pre class="prettyprint">{@code 49 * AmbientMode.AmbientController controller = AmbientMode.attachAmbientSupport(this); 50 * boolean isAmbient = controller.isAmbient(); 51 * }</pre> 52 * @deprecated please use {@link AmbientModeSupport} instead. 53 */ 54@Deprecated 55public final class AmbientMode extends Fragment { 56 private static final String TAG = "AmbientMode"; 57 58 /** 59 * Property in bundle passed to {@code AmbientCallback#onEnterAmbient(Bundle)} to indicate 60 * whether burn-in protection is required. When this property is set to true, views must be 61 * shifted around periodically in ambient mode. To ensure that content isn't shifted off 62 * the screen, avoid placing content within 10 pixels of the edge of the screen. Activities 63 * should also avoid solid white areas to prevent pixel burn-in. Both of these requirements 64 * only apply in ambient mode, and only when this property is set to true. 65 */ 66 public static final String EXTRA_BURN_IN_PROTECTION = 67 WearableActivityController.EXTRA_BURN_IN_PROTECTION; 68 69 /** 70 * Property in bundle passed to {@code AmbientCallback#onEnterAmbient(Bundle)} to indicate 71 * whether the device has low-bit ambient mode. When this property is set to true, the screen 72 * supports fewer bits for each color in ambient mode. In this case, activities should disable 73 * anti-aliasing in ambient mode. 74 */ 75 public static final String EXTRA_LOWBIT_AMBIENT = 76 WearableActivityController.EXTRA_LOWBIT_AMBIENT; 77 78 /** 79 * Fragment tag used by default when adding {@link AmbientMode} to add ambient support to an 80 * {@link Activity}. 81 */ 82 public static final String FRAGMENT_TAG = "android.support.wearable.ambient.AmbientMode"; 83 84 /** 85 * Interface for any {@link Activity} that wishes to implement Ambient Mode. Use the 86 * {@link #getAmbientCallback()} method to return and {@link AmbientCallback} which can be used 87 * to bind the {@link AmbientMode} to the instantiation of this interface. 88 * <p> 89 * <pre class="prettyprint">{@code 90 * return new AmbientMode.AmbientCallback() { 91 * public void onEnterAmbient(Bundle ambientDetails) {...} 92 * public void onExitAmbient(Bundle ambientDetails) {...} 93 * } 94 * }</pre> 95 */ 96 public interface AmbientCallbackProvider { 97 /** 98 * @return the {@link AmbientCallback} to be used by this class to communicate with the 99 * entity interested in ambient events. 100 */ 101 AmbientCallback getAmbientCallback(); 102 } 103 104 /** 105 * Callback to receive ambient mode state changes. It must be used by all users of AmbientMode. 106 */ 107 public abstract static class AmbientCallback { 108 /** 109 * Called when an activity is entering ambient mode. This event is sent while an activity is 110 * running (after onResume, before onPause). All drawing should complete by the conclusion 111 * of this method. Note that {@code invalidate()} calls will be executed before resuming 112 * lower-power mode. 113 * 114 * @param ambientDetails bundle containing information about the display being used. 115 * It includes information about low-bit color and burn-in protection. 116 */ 117 public void onEnterAmbient(Bundle ambientDetails) {} 118 119 /** 120 * Called when the system is updating the display for ambient mode. Activities may use this 121 * opportunity to update or invalidate views. 122 */ 123 public void onUpdateAmbient() {} 124 125 /** 126 * Called when an activity should exit ambient mode. This event is sent while an activity is 127 * running (after onResume, before onPause). 128 */ 129 public void onExitAmbient() {} 130 } 131 132 private final AmbientDelegate.AmbientCallback mCallback = 133 new AmbientDelegate.AmbientCallback() { 134 @Override 135 public void onEnterAmbient(Bundle ambientDetails) { 136 if (mSuppliedCallback != null) { 137 mSuppliedCallback.onEnterAmbient(ambientDetails); 138 } 139 } 140 141 @Override 142 public void onExitAmbient() { 143 if (mSuppliedCallback != null) { 144 mSuppliedCallback.onExitAmbient(); 145 } 146 } 147 148 @Override 149 public void onUpdateAmbient() { 150 if (mSuppliedCallback != null) { 151 mSuppliedCallback.onUpdateAmbient(); 152 } 153 } 154 }; 155 private AmbientDelegate mDelegate; 156 @Nullable 157 private AmbientCallback mSuppliedCallback; 158 private AmbientController mController; 159 160 /** 161 * Constructor 162 */ 163 public AmbientMode() { 164 mController = new AmbientController(); 165 } 166 167 @Override 168 @CallSuper 169 public void onAttach(Context context) { 170 super.onAttach(context); 171 mDelegate = new AmbientDelegate(getActivity(), new WearableControllerProvider(), mCallback); 172 173 if (context instanceof AmbientCallbackProvider) { 174 mSuppliedCallback = ((AmbientCallbackProvider) context).getAmbientCallback(); 175 } else { 176 Log.w(TAG, "No callback provided - enabling only smart resume"); 177 } 178 } 179 180 @Override 181 @CallSuper 182 public void onCreate(Bundle savedInstanceState) { 183 super.onCreate(savedInstanceState); 184 mDelegate.onCreate(); 185 if (mSuppliedCallback != null) { 186 mDelegate.setAmbientEnabled(); 187 } 188 } 189 190 @Override 191 @CallSuper 192 public void onResume() { 193 super.onResume(); 194 mDelegate.onResume(); 195 } 196 197 @Override 198 @CallSuper 199 public void onPause() { 200 mDelegate.onPause(); 201 super.onPause(); 202 } 203 204 @Override 205 @CallSuper 206 public void onStop() { 207 mDelegate.onStop(); 208 super.onStop(); 209 } 210 211 @Override 212 @CallSuper 213 public void onDestroy() { 214 mDelegate.onDestroy(); 215 super.onDestroy(); 216 } 217 218 @Override 219 @CallSuper 220 public void onDetach() { 221 mDelegate = null; 222 super.onDetach(); 223 } 224 225 /** 226 * Attach ambient support to the given activity. Calling this method with an Activity 227 * implementing the {@link AmbientCallbackProvider} interface will provide you with an 228 * opportunity to react to ambient events such as {@code onEnterAmbient}. Alternatively, 229 * you can call this method with an Activity which does not implement 230 * the {@link AmbientCallbackProvider} interface and that will only enable the auto-resume 231 * functionality. This is equivalent to providing (@code null} from 232 * the {@link AmbientCallbackProvider}. 233 * 234 * @param activity the activity to attach ambient support to. 235 * @return the associated {@link AmbientController} which can be used to query the state of 236 * ambient mode. 237 */ 238 public static <T extends Activity> AmbientController attachAmbientSupport(T activity) { 239 FragmentManager fragmentManager = activity.getFragmentManager(); 240 AmbientMode ambientFragment = (AmbientMode) fragmentManager.findFragmentByTag(FRAGMENT_TAG); 241 if (ambientFragment == null) { 242 AmbientMode fragment = new AmbientMode(); 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 AmbientMode}. 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