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 
17 package android.app;
18 
19 import static android.app.ActivityThread.isSystem;
20 import static android.app.WindowConfigurationProto.ACTIVITY_TYPE;
21 import static android.app.WindowConfigurationProto.APP_BOUNDS;
22 import static android.app.WindowConfigurationProto.WINDOWING_MODE;
23 
24 import android.annotation.IntDef;
25 import android.annotation.NonNull;
26 import android.annotation.TestApi;
27 import android.content.res.Configuration;
28 import android.graphics.Rect;
29 import android.os.Parcel;
30 import android.os.Parcelable;
31 import android.util.proto.ProtoOutputStream;
32 import 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
42 public 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 
WindowConfiguration()152     public WindowConfiguration() {
153         unset();
154     }
155 
156     /** @hide */
WindowConfiguration(WindowConfiguration configuration)157     public WindowConfiguration(WindowConfiguration configuration) {
158         setTo(configuration);
159     }
160 
WindowConfiguration(Parcel in)161     private WindowConfiguration(Parcel in) {
162         readFromParcel(in);
163     }
164 
165     @Override
writeToParcel(Parcel dest, int flags)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 
readFromParcel(Parcel source)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
describeContents()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      */
setBounds(Rect rect)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      */
setAppBounds(Rect rect)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      */
setAppBounds(int left, int top, int right, int bottom)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) */
getAppBounds()239     public Rect getAppBounds() {
240         return mAppBounds;
241     }
242 
243     /** @see #setBounds(Rect) */
getBounds()244     public Rect getBounds() {
245         return mBounds;
246     }
247 
setWindowingMode(@indowingMode int windowingMode)248     public void setWindowingMode(@WindowingMode int windowingMode) {
249         mWindowingMode = windowingMode;
250     }
251 
252     @WindowingMode
getWindowingMode()253     public int getWindowingMode() {
254         return mWindowingMode;
255     }
256 
setActivityType(@ctivityType int activityType)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
getActivityType()275     public int getActivityType() {
276         return mActivityType;
277     }
278 
setTo(WindowConfiguration other)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 */
unset()288     public void unset() {
289         setToDefaults();
290     }
291 
292     /** @hide */
setToDefaults()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      */
updateFrom(@onNull WindowConfiguration delta)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      */
diff(WindowConfiguration other, boolean compareUndefined)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
compareTo(WindowConfiguration that)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
equals(Object that)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
hashCode()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
toString()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      */
writeToProto(ProtoOutputStream protoOutputStream, long fieldId)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      */
hasWindowShadow()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      */
hasWindowDecorCaption()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      */
canResizeTask()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 */
persistTaskBounds()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      */
tasksAreFloating()497     public boolean tasksAreFloating() {
498         return isFloating(mWindowingMode);
499     }
500 
501     /**
502      * Returns true if the windowingMode represents a floating window.
503      * @hide
504      */
isFloating(int windowingMode)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      */
canReceiveKeys()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      */
isAlwaysOnTop()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      */
keepVisibleDeadAppWindowOnScreen()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      */
useWindowFrameForBackdrop()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      */
windowsAreScaleable()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      */
hasMovementAnimations()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      */
supportSplitScreenWindowingMode()567     public boolean supportSplitScreenWindowingMode() {
568         return supportSplitScreenWindowingMode(mActivityType);
569     }
570 
571     /** @hide */
supportSplitScreenWindowingMode(int activityType)572     public static boolean supportSplitScreenWindowingMode(int activityType) {
573         return activityType != ACTIVITY_TYPE_ASSISTANT;
574     }
575 
576     /** @hide */
windowingModeToString(@indowingMode int windowingMode)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 */
activityTypeToString(@ctivityType int applicationType)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