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