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
17package android.app;
18
19import static android.app.ActivityThread.isSystem;
20import static android.app.WindowConfigurationProto.ACTIVITY_TYPE;
21import static android.app.WindowConfigurationProto.APP_BOUNDS;
22import static android.app.WindowConfigurationProto.WINDOWING_MODE;
23
24import android.annotation.IntDef;
25import android.annotation.NonNull;
26import android.annotation.TestApi;
27import android.content.res.Configuration;
28import android.graphics.Rect;
29import android.os.Parcel;
30import android.os.Parcelable;
31import android.util.proto.ProtoOutputStream;
32import android.view.DisplayInfo;
33
34/**
35 * Class that contains windowing configuration/state for other objects that contain windows directly
36 * or indirectly. E.g. Activities, Task, Displays, ...
37 * The test class is {@link com.android.server.wm.WindowConfigurationTests} which must be kept
38 * up-to-date and ran anytime changes are made to this class.
39 * @hide
40 */
41@TestApi
42public class WindowConfiguration implements Parcelable, Comparable<WindowConfiguration> {
43    /**
44     * bounds that can differ from app bounds, which may include things such as insets.
45     *
46     * TODO: Investigate combining with {@link mAppBounds}. Can the latter be a product of the
47     * former?
48     */
49    private Rect mBounds = new Rect();
50
51    /**
52     * {@link android.graphics.Rect} defining app bounds. The dimensions override usages of
53     * {@link DisplayInfo#appHeight} and {@link DisplayInfo#appWidth} and mirrors these values at
54     * the display level. Lower levels can override these values to provide custom bounds to enforce
55     * features such as a max aspect ratio.
56     */
57    private Rect mAppBounds;
58
59    /** The current windowing mode of the configuration. */
60    private @WindowingMode int mWindowingMode;
61
62    /** Windowing mode is currently not defined. */
63    public static final int WINDOWING_MODE_UNDEFINED = 0;
64    /** Occupies the full area of the screen or the parent container. */
65    public static final int WINDOWING_MODE_FULLSCREEN = 1;
66    /** Always on-top (always visible). of other siblings in its parent container. */
67    public static final int WINDOWING_MODE_PINNED = 2;
68    /** The primary container driving the screen to be in split-screen mode. */
69    public static final int WINDOWING_MODE_SPLIT_SCREEN_PRIMARY = 3;
70    /**
71     * The containers adjacent to the {@link #WINDOWING_MODE_SPLIT_SCREEN_PRIMARY} container in
72     * split-screen mode.
73     * NOTE: Containers launched with the windowing mode with APIs like
74     * {@link ActivityOptions#setLaunchWindowingMode(int)} will be launched in
75     * {@link #WINDOWING_MODE_FULLSCREEN} if the display isn't currently in split-screen windowing
76     * mode
77     * @see #WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY
78     */
79    public static final int WINDOWING_MODE_SPLIT_SCREEN_SECONDARY = 4;
80    /**
81     * Alias for {@link #WINDOWING_MODE_SPLIT_SCREEN_SECONDARY} that makes it clear that the usage
82     * points for APIs like {@link ActivityOptions#setLaunchWindowingMode(int)} that the container
83     * will launch into fullscreen or split-screen secondary depending on if the device is currently
84     * in fullscreen mode or split-screen mode.
85     */
86    public static final int WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY =
87            WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
88    /** Can be freely resized within its parent container. */
89    public static final int WINDOWING_MODE_FREEFORM = 5;
90
91    /** @hide */
92    @IntDef(prefix = { "WINDOWING_MODE_" }, value = {
93            WINDOWING_MODE_UNDEFINED,
94            WINDOWING_MODE_FULLSCREEN,
95            WINDOWING_MODE_PINNED,
96            WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
97            WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
98            WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY,
99            WINDOWING_MODE_FREEFORM,
100    })
101    public @interface WindowingMode {}
102
103    /** The current activity type of the configuration. */
104    private @ActivityType int mActivityType;
105
106    /** Activity type is currently not defined. */
107    public static final int ACTIVITY_TYPE_UNDEFINED = 0;
108    /** Standard activity type. Nothing special about the activity... */
109    public static final int ACTIVITY_TYPE_STANDARD = 1;
110    /** Home/Launcher activity type. */
111    public static final int ACTIVITY_TYPE_HOME = 2;
112    /** Recents/Overview activity type. There is only one activity with this type in the system. */
113    public static final int ACTIVITY_TYPE_RECENTS = 3;
114    /** Assistant activity type. */
115    public static final int ACTIVITY_TYPE_ASSISTANT = 4;
116
117    /** @hide */
118    @IntDef(prefix = { "ACTIVITY_TYPE_" }, value = {
119            ACTIVITY_TYPE_UNDEFINED,
120            ACTIVITY_TYPE_STANDARD,
121            ACTIVITY_TYPE_HOME,
122            ACTIVITY_TYPE_RECENTS,
123            ACTIVITY_TYPE_ASSISTANT,
124    })
125    public @interface ActivityType {}
126
127    /** Bit that indicates that the {@link #mBounds} changed.
128     * @hide */
129    public static final int WINDOW_CONFIG_BOUNDS = 1 << 0;
130    /** Bit that indicates that the {@link #mAppBounds} changed.
131     * @hide */
132    public static final int WINDOW_CONFIG_APP_BOUNDS = 1 << 1;
133    /** Bit that indicates that the {@link #mWindowingMode} changed.
134     * @hide */
135    public static final int WINDOW_CONFIG_WINDOWING_MODE = 1 << 2;
136    /** Bit that indicates that the {@link #mActivityType} changed.
137     * @hide */
138    public static final int WINDOW_CONFIG_ACTIVITY_TYPE = 1 << 3;
139
140    /** @hide */
141    @IntDef(flag = true, prefix = { "WINDOW_CONFIG_" }, value = {
142            WINDOW_CONFIG_BOUNDS,
143            WINDOW_CONFIG_APP_BOUNDS,
144            WINDOW_CONFIG_WINDOWING_MODE,
145            WINDOW_CONFIG_ACTIVITY_TYPE
146    })
147    public @interface WindowConfig {}
148
149    /** @hide */
150    public static final int PINNED_WINDOWING_MODE_ELEVATION_IN_DIP = 5;
151
152    public WindowConfiguration() {
153        unset();
154    }
155
156    /** @hide */
157    public WindowConfiguration(WindowConfiguration configuration) {
158        setTo(configuration);
159    }
160
161    private WindowConfiguration(Parcel in) {
162        readFromParcel(in);
163    }
164
165    @Override
166    public void writeToParcel(Parcel dest, int flags) {
167        dest.writeParcelable(mBounds, flags);
168        dest.writeParcelable(mAppBounds, flags);
169        dest.writeInt(mWindowingMode);
170        dest.writeInt(mActivityType);
171    }
172
173    private void readFromParcel(Parcel source) {
174        mBounds = source.readParcelable(Rect.class.getClassLoader());
175        mAppBounds = source.readParcelable(Rect.class.getClassLoader());
176        mWindowingMode = source.readInt();
177        mActivityType = source.readInt();
178    }
179
180    @Override
181    public int describeContents() {
182        return 0;
183    }
184
185    /** @hide */
186    public static final Creator<WindowConfiguration> CREATOR = new Creator<WindowConfiguration>() {
187        @Override
188        public WindowConfiguration createFromParcel(Parcel in) {
189            return new WindowConfiguration(in);
190        }
191
192        @Override
193        public WindowConfiguration[] newArray(int size) {
194            return new WindowConfiguration[size];
195        }
196    };
197
198    /**
199     * Sets the bounds to the provided {@link Rect}.
200     * @param rect the new bounds value.
201     */
202    public void setBounds(Rect rect) {
203        if (rect == null) {
204            mBounds.setEmpty();
205            return;
206        }
207
208        mBounds.set(rect);
209    }
210
211    /**
212     * Set {@link #mAppBounds} to the input Rect.
213     * @param rect The rect value to set {@link #mAppBounds} to.
214     * @see #getAppBounds()
215     */
216    public void setAppBounds(Rect rect) {
217        if (rect == null) {
218            mAppBounds = null;
219            return;
220        }
221
222        setAppBounds(rect.left, rect.top, rect.right, rect.bottom);
223    }
224
225    /**
226     * @see #setAppBounds(Rect)
227     * @see #getAppBounds()
228     * @hide
229     */
230    public void setAppBounds(int left, int top, int right, int bottom) {
231        if (mAppBounds == null) {
232            mAppBounds = new Rect();
233        }
234
235        mAppBounds.set(left, top, right, bottom);
236    }
237
238    /** @see #setAppBounds(Rect) */
239    public Rect getAppBounds() {
240        return mAppBounds;
241    }
242
243    /** @see #setBounds(Rect) */
244    public Rect getBounds() {
245        return mBounds;
246    }
247
248    public void setWindowingMode(@WindowingMode int windowingMode) {
249        mWindowingMode = windowingMode;
250    }
251
252    @WindowingMode
253    public int getWindowingMode() {
254        return mWindowingMode;
255    }
256
257    public void setActivityType(@ActivityType int activityType) {
258        if (mActivityType == activityType) {
259            return;
260        }
261
262        // Error check within system server that we are not changing activity type which can be
263        // dangerous. It is okay for things to change in the application process as it doesn't
264        // affect how other things is the system is managed.
265        if (isSystem()
266                && mActivityType != ACTIVITY_TYPE_UNDEFINED
267                && activityType != ACTIVITY_TYPE_UNDEFINED) {
268            throw new IllegalStateException("Can't change activity type once set: " + this
269                    + " activityType=" + activityTypeToString(activityType));
270        }
271        mActivityType = activityType;
272    }
273
274    @ActivityType
275    public int getActivityType() {
276        return mActivityType;
277    }
278
279    public void setTo(WindowConfiguration other) {
280        setBounds(other.mBounds);
281        setAppBounds(other.mAppBounds);
282        setWindowingMode(other.mWindowingMode);
283        setActivityType(other.mActivityType);
284    }
285
286    /** Set this object to completely undefined.
287     * @hide */
288    public void unset() {
289        setToDefaults();
290    }
291
292    /** @hide */
293    public void setToDefaults() {
294        setAppBounds(null);
295        setBounds(null);
296        setWindowingMode(WINDOWING_MODE_UNDEFINED);
297        setActivityType(ACTIVITY_TYPE_UNDEFINED);
298    }
299
300    /**
301     * Copies the fields from delta into this Configuration object, keeping
302     * track of which ones have changed. Any undefined fields in {@code delta}
303     * are ignored and not copied in to the current Configuration.
304     *
305     * @return a bit mask of the changed fields, as per {@link #diff}
306     * @hide
307     */
308    public @WindowConfig int updateFrom(@NonNull WindowConfiguration delta) {
309        int changed = 0;
310        // Only allow override if bounds is not empty
311        if (!delta.mBounds.isEmpty() && !delta.mBounds.equals(mBounds)) {
312            changed |= WINDOW_CONFIG_BOUNDS;
313            setBounds(delta.mBounds);
314        }
315        if (delta.mAppBounds != null && !delta.mAppBounds.equals(mAppBounds)) {
316            changed |= WINDOW_CONFIG_APP_BOUNDS;
317            setAppBounds(delta.mAppBounds);
318        }
319        if (delta.mWindowingMode != WINDOWING_MODE_UNDEFINED
320                && mWindowingMode != delta.mWindowingMode) {
321            changed |= WINDOW_CONFIG_WINDOWING_MODE;
322            setWindowingMode(delta.mWindowingMode);
323        }
324        if (delta.mActivityType != ACTIVITY_TYPE_UNDEFINED
325                && mActivityType != delta.mActivityType) {
326            changed |= WINDOW_CONFIG_ACTIVITY_TYPE;
327            setActivityType(delta.mActivityType);
328        }
329        return changed;
330    }
331
332    /**
333     * Return a bit mask of the differences between this Configuration object and the given one.
334     * Does not change the values of either. Any undefined fields in <var>other</var> are ignored.
335     * @param other The configuration to diff against.
336     * @param compareUndefined If undefined values should be compared.
337     * @return Returns a bit mask indicating which configuration
338     * values has changed, containing any combination of {@link WindowConfig} flags.
339     *
340     * @see Configuration#diff(Configuration)
341     * @hide
342     */
343    public @WindowConfig long diff(WindowConfiguration other, boolean compareUndefined) {
344        long changes = 0;
345
346        if (!mBounds.equals(other.mBounds)) {
347            changes |= WINDOW_CONFIG_BOUNDS;
348        }
349
350        // Make sure that one of the values is not null and that they are not equal.
351        if ((compareUndefined || other.mAppBounds != null)
352                && mAppBounds != other.mAppBounds
353                && (mAppBounds == null || !mAppBounds.equals(other.mAppBounds))) {
354            changes |= WINDOW_CONFIG_APP_BOUNDS;
355        }
356
357        if ((compareUndefined || other.mWindowingMode != WINDOWING_MODE_UNDEFINED)
358                && mWindowingMode != other.mWindowingMode) {
359            changes |= WINDOW_CONFIG_WINDOWING_MODE;
360        }
361
362        if ((compareUndefined || other.mActivityType != ACTIVITY_TYPE_UNDEFINED)
363                && mActivityType != other.mActivityType) {
364            changes |= WINDOW_CONFIG_ACTIVITY_TYPE;
365        }
366
367        return changes;
368    }
369
370    @Override
371    public int compareTo(WindowConfiguration that) {
372        int n = 0;
373        if (mAppBounds == null && that.mAppBounds != null) {
374            return 1;
375        } else if (mAppBounds != null && that.mAppBounds == null) {
376            return -1;
377        } else if (mAppBounds != null && that.mAppBounds != null) {
378            n = mAppBounds.left - that.mAppBounds.left;
379            if (n != 0) return n;
380            n = mAppBounds.top - that.mAppBounds.top;
381            if (n != 0) return n;
382            n = mAppBounds.right - that.mAppBounds.right;
383            if (n != 0) return n;
384            n = mAppBounds.bottom - that.mAppBounds.bottom;
385            if (n != 0) return n;
386        }
387
388        n = mBounds.left - that.mBounds.left;
389        if (n != 0) return n;
390        n = mBounds.top - that.mBounds.top;
391        if (n != 0) return n;
392        n = mBounds.right - that.mBounds.right;
393        if (n != 0) return n;
394        n = mBounds.bottom - that.mBounds.bottom;
395        if (n != 0) return n;
396
397        n = mWindowingMode - that.mWindowingMode;
398        if (n != 0) return n;
399        n = mActivityType - that.mActivityType;
400        if (n != 0) return n;
401
402        // if (n != 0) return n;
403        return n;
404    }
405
406    /** @hide */
407    @Override
408    public boolean equals(Object that) {
409        if (that == null) return false;
410        if (that == this) return true;
411        if (!(that instanceof WindowConfiguration)) {
412            return false;
413        }
414        return this.compareTo((WindowConfiguration) that) == 0;
415    }
416
417    /** @hide */
418    @Override
419    public int hashCode() {
420        int result = 0;
421        if (mAppBounds != null) {
422            result = 31 * result + mAppBounds.hashCode();
423        }
424        result = 31 * result + mBounds.hashCode();
425
426        result = 31 * result + mWindowingMode;
427        result = 31 * result + mActivityType;
428        return result;
429    }
430
431    /** @hide */
432    @Override
433    public String toString() {
434        return "{ mBounds=" + mBounds
435                + " mAppBounds=" + mAppBounds
436                + " mWindowingMode=" + windowingModeToString(mWindowingMode)
437                + " mActivityType=" + activityTypeToString(mActivityType) + "}";
438    }
439
440    /**
441     * Write to a protocol buffer output stream.
442     * Protocol buffer message definition at {@link android.app.WindowConfigurationProto}
443     *
444     * @param protoOutputStream Stream to write the WindowConfiguration object to.
445     * @param fieldId           Field Id of the WindowConfiguration as defined in the parent message
446     * @hide
447     */
448    public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) {
449        final long token = protoOutputStream.start(fieldId);
450        if (mAppBounds != null) {
451            mAppBounds.writeToProto(protoOutputStream, APP_BOUNDS);
452        }
453        protoOutputStream.write(WINDOWING_MODE, mWindowingMode);
454        protoOutputStream.write(ACTIVITY_TYPE, mActivityType);
455        protoOutputStream.end(token);
456    }
457
458    /**
459     * Returns true if the activities associated with this window configuration display a shadow
460     * around their border.
461     * @hide
462     */
463    public boolean hasWindowShadow() {
464        return tasksAreFloating();
465    }
466
467    /**
468     * Returns true if the activities associated with this window configuration display a decor
469     * view.
470     * @hide
471     */
472    public boolean hasWindowDecorCaption() {
473        return mWindowingMode == WINDOWING_MODE_FREEFORM;
474    }
475
476    /**
477     * Returns true if the tasks associated with this window configuration can be resized
478     * independently of their parent container.
479     * @hide
480     */
481    public boolean canResizeTask() {
482        return mWindowingMode == WINDOWING_MODE_FREEFORM;
483    }
484
485    /** Returns true if the task bounds should persist across power cycles.
486     * @hide */
487    public boolean persistTaskBounds() {
488        return mWindowingMode == WINDOWING_MODE_FREEFORM;
489    }
490
491    /**
492     * Returns true if the tasks associated with this window configuration are floating.
493     * Floating tasks are laid out differently as they are allowed to extend past the display bounds
494     * without overscan insets.
495     * @hide
496     */
497    public boolean tasksAreFloating() {
498        return isFloating(mWindowingMode);
499    }
500
501    /**
502     * Returns true if the windowingMode represents a floating window.
503     * @hide
504     */
505    public static boolean isFloating(int windowingMode) {
506        return windowingMode == WINDOWING_MODE_FREEFORM || windowingMode == WINDOWING_MODE_PINNED;
507    }
508
509    /**
510     * Returns true if the windows associated with this window configuration can receive input keys.
511     * @hide
512     */
513    public boolean canReceiveKeys() {
514        return mWindowingMode != WINDOWING_MODE_PINNED;
515    }
516
517    /**
518     * Returns true if the container associated with this window configuration is always-on-top of
519     * its siblings.
520     * @hide
521     */
522    public boolean isAlwaysOnTop() {
523        return mWindowingMode == WINDOWING_MODE_PINNED;
524    }
525
526    /**
527     * Returns true if any visible windows belonging to apps with this window configuration should
528     * be kept on screen when the app is killed due to something like the low memory killer.
529     * @hide
530     */
531    public boolean keepVisibleDeadAppWindowOnScreen() {
532        return mWindowingMode != WINDOWING_MODE_PINNED;
533    }
534
535    /**
536     * Returns true if the backdrop on the client side should match the frame of the window.
537     * Returns false, if the backdrop should be fullscreen.
538     * @hide
539     */
540    public boolean useWindowFrameForBackdrop() {
541        return mWindowingMode == WINDOWING_MODE_FREEFORM || mWindowingMode == WINDOWING_MODE_PINNED;
542    }
543
544    /**
545     * Returns true if this container may be scaled without resizing, and windows within may need
546     * to be configured as such.
547     * @hide
548     */
549    public boolean windowsAreScaleable() {
550        return mWindowingMode == WINDOWING_MODE_PINNED;
551    }
552
553    /**
554     * Returns true if windows in this container should be given move animations by default.
555     * @hide
556     */
557    public boolean hasMovementAnimations() {
558        return mWindowingMode != WINDOWING_MODE_PINNED;
559    }
560
561    /**
562     * Returns true if this container can be put in either
563     * {@link #WINDOWING_MODE_SPLIT_SCREEN_PRIMARY} or
564     * {@link #WINDOWING_MODE_SPLIT_SCREEN_SECONDARY} windowing modes based on its current state.
565     * @hide
566     */
567    public boolean supportSplitScreenWindowingMode() {
568        return supportSplitScreenWindowingMode(mActivityType);
569    }
570
571    /** @hide */
572    public static boolean supportSplitScreenWindowingMode(int activityType) {
573        return activityType != ACTIVITY_TYPE_ASSISTANT;
574    }
575
576    /** @hide */
577    public static String windowingModeToString(@WindowingMode int windowingMode) {
578        switch (windowingMode) {
579            case WINDOWING_MODE_UNDEFINED: return "undefined";
580            case WINDOWING_MODE_FULLSCREEN: return "fullscreen";
581            case WINDOWING_MODE_PINNED: return "pinned";
582            case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY: return "split-screen-primary";
583            case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY: return "split-screen-secondary";
584            case WINDOWING_MODE_FREEFORM: return "freeform";
585        }
586        return String.valueOf(windowingMode);
587    }
588
589    /** @hide */
590    public static String activityTypeToString(@ActivityType int applicationType) {
591        switch (applicationType) {
592            case ACTIVITY_TYPE_UNDEFINED: return "undefined";
593            case ACTIVITY_TYPE_STANDARD: return "standard";
594            case ACTIVITY_TYPE_HOME: return "home";
595            case ACTIVITY_TYPE_RECENTS: return "recents";
596            case ACTIVITY_TYPE_ASSISTANT: return "assistant";
597        }
598        return String.valueOf(applicationType);
599    }
600}
601