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 com.android.server.wm;
18 
19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
21 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
22 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
23 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
24 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
25 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
26 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
27 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
28 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
29 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
30 import static android.app.WindowConfiguration.activityTypeToString;
31 import static android.app.WindowConfiguration.windowingModeToString;
32 import static com.android.server.wm.ConfigurationContainerProto.FULL_CONFIGURATION;
33 import static com.android.server.wm.ConfigurationContainerProto.MERGED_OVERRIDE_CONFIGURATION;
34 import static com.android.server.wm.ConfigurationContainerProto.OVERRIDE_CONFIGURATION;
35 
36 import android.annotation.CallSuper;
37 import android.app.WindowConfiguration;
38 import android.content.res.Configuration;
39 import android.graphics.Rect;
40 import android.util.proto.ProtoOutputStream;
41 
42 import java.io.PrintWriter;
43 import java.util.ArrayList;
44 
45 /**
46  * Contains common logic for classes that have override configurations and are organized in a
47  * hierarchy.
48  */
49 public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
50     /**
51      * {@link #Rect} returned from {@link #getOverrideBounds()} to prevent original value from being
52      * set directly.
53      */
54     private Rect mReturnBounds = new Rect();
55 
56     /** Contains override configuration settings applied to this configuration container. */
57     private Configuration mOverrideConfiguration = new Configuration();
58 
59     /** True if mOverrideConfiguration is not empty */
60     private boolean mHasOverrideConfiguration;
61 
62     /**
63      * Contains full configuration applied to this configuration container. Corresponds to full
64      * parent's config with applied {@link #mOverrideConfiguration}.
65      */
66     private Configuration mFullConfiguration = new Configuration();
67 
68     /**
69      * Contains merged override configuration settings from the top of the hierarchy down to this
70      * particular instance. It is different from {@link #mFullConfiguration} because it starts from
71      * topmost container's override config instead of global config.
72      */
73     private Configuration mMergedOverrideConfiguration = new Configuration();
74 
75     private ArrayList<ConfigurationContainerListener> mChangeListeners = new ArrayList<>();
76 
77     // TODO: Can't have ag/2592611 soon enough!
78     private final Configuration mTmpConfig = new Configuration();
79 
80     // Used for setting bounds
81     private final Rect mTmpRect = new Rect();
82 
83     static final int BOUNDS_CHANGE_NONE = 0;
84     // Return value from {@link setBounds} indicating the position of the override bounds changed.
85     static final int BOUNDS_CHANGE_POSITION = 1;
86     // Return value from {@link setBounds} indicating the size of the override bounds changed.
87     static final int BOUNDS_CHANGE_SIZE = 1 << 1;
88 
89 
90     /**
91      * Returns full configuration applied to this configuration container.
92      * This method should be used for getting settings applied in each particular level of the
93      * hierarchy.
94      */
getConfiguration()95     public Configuration getConfiguration() {
96         return mFullConfiguration;
97     }
98 
99     /**
100      * Notify that parent config changed and we need to update full configuration.
101      * @see #mFullConfiguration
102      */
onConfigurationChanged(Configuration newParentConfig)103     public void onConfigurationChanged(Configuration newParentConfig) {
104         mFullConfiguration.setTo(newParentConfig);
105         mFullConfiguration.updateFrom(mOverrideConfiguration);
106         for (int i = getChildCount() - 1; i >= 0; --i) {
107             final ConfigurationContainer child = getChildAt(i);
108             child.onConfigurationChanged(mFullConfiguration);
109         }
110     }
111 
112     /** Returns override configuration applied to this configuration container. */
getOverrideConfiguration()113     public Configuration getOverrideConfiguration() {
114         return mOverrideConfiguration;
115     }
116 
117     /**
118      * Update override configuration and recalculate full config.
119      * @see #mOverrideConfiguration
120      * @see #mFullConfiguration
121      */
onOverrideConfigurationChanged(Configuration overrideConfiguration)122     public void onOverrideConfigurationChanged(Configuration overrideConfiguration) {
123         // Pre-compute this here, so we don't need to go through the entire Configuration when
124         // writing to proto (which has significant cost if we write a lot of empty configurations).
125         mHasOverrideConfiguration = !Configuration.EMPTY.equals(overrideConfiguration);
126         mOverrideConfiguration.setTo(overrideConfiguration);
127         // Update full configuration of this container and all its children.
128         final ConfigurationContainer parent = getParent();
129         onConfigurationChanged(parent != null ? parent.getConfiguration() : Configuration.EMPTY);
130         // Update merged override config of this container and all its children.
131         onMergedOverrideConfigurationChanged();
132 
133         // Use the updated override configuration to notify listeners.
134         mTmpConfig.setTo(mOverrideConfiguration);
135         // Inform listeners of the change.
136         for (int i = mChangeListeners.size() - 1; i >=0; --i) {
137             mChangeListeners.get(i).onOverrideConfigurationChanged(mTmpConfig);
138         }
139     }
140 
141     /**
142      * Get merged override configuration from the top of the hierarchy down to this particular
143      * instance. This should be reported to client as override config.
144      */
getMergedOverrideConfiguration()145     public Configuration getMergedOverrideConfiguration() {
146         return mMergedOverrideConfiguration;
147     }
148 
149     /**
150      * Update merged override configuration based on corresponding parent's config and notify all
151      * its children. If there is no parent, merged override configuration will set equal to current
152      * override config.
153      * @see #mMergedOverrideConfiguration
154      */
onMergedOverrideConfigurationChanged()155     void onMergedOverrideConfigurationChanged() {
156         final ConfigurationContainer parent = getParent();
157         if (parent != null) {
158             mMergedOverrideConfiguration.setTo(parent.getMergedOverrideConfiguration());
159             mMergedOverrideConfiguration.updateFrom(mOverrideConfiguration);
160         } else {
161             mMergedOverrideConfiguration.setTo(mOverrideConfiguration);
162         }
163         for (int i = getChildCount() - 1; i >= 0; --i) {
164             final ConfigurationContainer child = getChildAt(i);
165             child.onMergedOverrideConfigurationChanged();
166         }
167     }
168 
169     /**
170      * Indicates whether this container has not specified any bounds different from its parent. In
171      * this case, it will inherit the bounds of the first ancestor which specifies a bounds.
172      * @return {@code true} if no explicit bounds have been set at this container level.
173      *         {@code false} otherwise.
174      */
matchParentBounds()175     public boolean matchParentBounds() {
176         return getOverrideBounds().isEmpty();
177     }
178 
179     /**
180      * Returns whether the bounds specified is considered the same as the existing override bounds.
181      * This is either when the two bounds are equal or the override bounds is empty and the
182      * specified bounds is null.
183      *
184      * @return {@code true} if the bounds are equivalent, {@code false} otherwise
185      */
equivalentOverrideBounds(Rect bounds)186     public boolean equivalentOverrideBounds(Rect bounds) {
187         return equivalentBounds(getOverrideBounds(),  bounds);
188     }
189 
190     /**
191      * Returns whether the two bounds are equal to each other or are a combination of null or empty.
192      */
equivalentBounds(Rect bounds, Rect other)193     public static boolean equivalentBounds(Rect bounds, Rect other) {
194         return bounds == other
195                 || (bounds != null && (bounds.equals(other) || (bounds.isEmpty() && other == null)))
196                 || (other != null && other.isEmpty() && bounds == null);
197     }
198 
199     /**
200      * Returns the effective bounds of this container, inheriting the first non-empty bounds set in
201      * its ancestral hierarchy, including itself.
202      * @return
203      */
getBounds()204     public Rect getBounds() {
205         mReturnBounds.set(getConfiguration().windowConfiguration.getBounds());
206         return mReturnBounds;
207     }
208 
getBounds(Rect outBounds)209     public void getBounds(Rect outBounds) {
210         outBounds.set(getBounds());
211     }
212 
213     /**
214      * Returns the current bounds explicitly set on this container. The {@link Rect} handed back is
215      * shared for all calls to this method and should not be modified.
216      */
getOverrideBounds()217     public Rect getOverrideBounds() {
218         mReturnBounds.set(getOverrideConfiguration().windowConfiguration.getBounds());
219 
220         return mReturnBounds;
221     }
222 
223     /**
224      * Returns {@code true} if the {@link WindowConfiguration} in the override
225      * {@link Configuration} specifies bounds.
226      */
hasOverrideBounds()227     public boolean hasOverrideBounds() {
228         return !getOverrideBounds().isEmpty();
229     }
230 
231     /**
232      * Sets the passed in {@link Rect} to the current bounds.
233      * @see {@link #getOverrideBounds()}.
234      */
getOverrideBounds(Rect outBounds)235     public void getOverrideBounds(Rect outBounds) {
236         outBounds.set(getOverrideBounds());
237     }
238 
239     /**
240      * Sets the bounds at the current hierarchy level, overriding any bounds set on an ancestor.
241      * This value will be reported when {@link #getBounds()} and {@link #getOverrideBounds()}. If
242      * an empty {@link Rect} or null is specified, this container will be considered to match its
243      * parent bounds {@see #matchParentBounds} and will inherit bounds from its parent.
244      * @param bounds The bounds defining the container size.
245      * @return a bitmask representing the types of changes made to the bounds.
246      */
setBounds(Rect bounds)247     public int setBounds(Rect bounds) {
248         int boundsChange = diffOverrideBounds(bounds);
249 
250         if (boundsChange == BOUNDS_CHANGE_NONE) {
251             return boundsChange;
252         }
253 
254 
255         mTmpConfig.setTo(getOverrideConfiguration());
256         mTmpConfig.windowConfiguration.setBounds(bounds);
257         onOverrideConfigurationChanged(mTmpConfig);
258 
259         return boundsChange;
260     }
261 
setBounds(int left, int top, int right, int bottom)262     public int setBounds(int left, int top, int right, int bottom) {
263         mTmpRect.set(left, top, right, bottom);
264         return setBounds(mTmpRect);
265     }
266 
diffOverrideBounds(Rect bounds)267     int diffOverrideBounds(Rect bounds) {
268         if (equivalentOverrideBounds(bounds)) {
269             return BOUNDS_CHANGE_NONE;
270         }
271 
272         int boundsChange = BOUNDS_CHANGE_NONE;
273 
274         final Rect existingBounds = getOverrideBounds();
275 
276         if (bounds == null || existingBounds.left != bounds.left
277                 || existingBounds.top != bounds.top) {
278             boundsChange |= BOUNDS_CHANGE_POSITION;
279         }
280 
281         if (bounds == null || existingBounds.width() != bounds.width()
282                 || existingBounds.height() != bounds.height()) {
283             boundsChange |= BOUNDS_CHANGE_SIZE;
284         }
285 
286         return boundsChange;
287     }
288 
getWindowConfiguration()289     public WindowConfiguration getWindowConfiguration() {
290         return mFullConfiguration.windowConfiguration;
291     }
292 
293     /** Returns the windowing mode the configuration container is currently in. */
getWindowingMode()294     public int getWindowingMode() {
295         return mFullConfiguration.windowConfiguration.getWindowingMode();
296     }
297 
298     /** Sets the windowing mode for the configuration container. */
setWindowingMode( int windowingMode)299     public void setWindowingMode(/*@WindowConfiguration.WindowingMode*/ int windowingMode) {
300         mTmpConfig.setTo(getOverrideConfiguration());
301         mTmpConfig.windowConfiguration.setWindowingMode(windowingMode);
302         onOverrideConfigurationChanged(mTmpConfig);
303     }
304 
305     /**
306      * Returns true if this container is currently in multi-window mode. I.e. sharing the screen
307      * with another activity.
308      */
inMultiWindowMode()309     public boolean inMultiWindowMode() {
310         /*@WindowConfiguration.WindowingMode*/ int windowingMode =
311                 mFullConfiguration.windowConfiguration.getWindowingMode();
312         return windowingMode != WINDOWING_MODE_FULLSCREEN
313                 && windowingMode != WINDOWING_MODE_UNDEFINED;
314     }
315 
316     /** Returns true if this container is currently in split-screen windowing mode. */
inSplitScreenWindowingMode()317     public boolean inSplitScreenWindowingMode() {
318         /*@WindowConfiguration.WindowingMode*/ int windowingMode =
319                 mFullConfiguration.windowConfiguration.getWindowingMode();
320 
321         return windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
322                 || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
323     }
324 
325     /** Returns true if this container is currently in split-screen secondary windowing mode. */
inSplitScreenSecondaryWindowingMode()326     public boolean inSplitScreenSecondaryWindowingMode() {
327         /*@WindowConfiguration.WindowingMode*/ int windowingMode =
328                 mFullConfiguration.windowConfiguration.getWindowingMode();
329 
330         return windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
331     }
332 
inSplitScreenPrimaryWindowingMode()333     public boolean inSplitScreenPrimaryWindowingMode() {
334         return mFullConfiguration.windowConfiguration.getWindowingMode()
335                 == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
336     }
337 
338     /**
339      * Returns true if this container can be put in either
340      * {@link WindowConfiguration#WINDOWING_MODE_SPLIT_SCREEN_PRIMARY} or
341      * {@link WindowConfiguration##WINDOWING_MODE_SPLIT_SCREEN_SECONDARY} windowing modes based on
342      * its current state.
343      */
supportsSplitScreenWindowingMode()344     public boolean supportsSplitScreenWindowingMode() {
345         return mFullConfiguration.windowConfiguration.supportSplitScreenWindowingMode();
346     }
347 
inPinnedWindowingMode()348     public boolean inPinnedWindowingMode() {
349         return mFullConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_PINNED;
350     }
351 
inFreeformWindowingMode()352     public boolean inFreeformWindowingMode() {
353         return mFullConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FREEFORM;
354     }
355 
356     /** Returns the activity type associated with the the configuration container. */
357     /*@WindowConfiguration.ActivityType*/
getActivityType()358     public int getActivityType() {
359         return mFullConfiguration.windowConfiguration.getActivityType();
360     }
361 
362     /** Sets the activity type to associate with the configuration container. */
setActivityType( int activityType)363     public void setActivityType(/*@WindowConfiguration.ActivityType*/ int activityType) {
364         int currentActivityType = getActivityType();
365         if (currentActivityType == activityType) {
366             return;
367         }
368         if (currentActivityType != ACTIVITY_TYPE_UNDEFINED) {
369             throw new IllegalStateException("Can't change activity type once set: " + this
370                     + " activityType=" + activityTypeToString(activityType));
371         }
372         mTmpConfig.setTo(getOverrideConfiguration());
373         mTmpConfig.windowConfiguration.setActivityType(activityType);
374         onOverrideConfigurationChanged(mTmpConfig);
375     }
376 
isActivityTypeHome()377     public boolean isActivityTypeHome() {
378         return getActivityType() == ACTIVITY_TYPE_HOME;
379     }
380 
isActivityTypeRecents()381     public boolean isActivityTypeRecents() {
382         return getActivityType() == ACTIVITY_TYPE_RECENTS;
383     }
384 
isActivityTypeAssistant()385     public boolean isActivityTypeAssistant() {
386         return getActivityType() == ACTIVITY_TYPE_ASSISTANT;
387     }
388 
isActivityTypeStandard()389     public boolean isActivityTypeStandard() {
390         return getActivityType() == ACTIVITY_TYPE_STANDARD;
391     }
392 
isActivityTypeStandardOrUndefined()393     public boolean isActivityTypeStandardOrUndefined() {
394         /*@WindowConfiguration.ActivityType*/ final int activityType = getActivityType();
395         return activityType == ACTIVITY_TYPE_STANDARD || activityType == ACTIVITY_TYPE_UNDEFINED;
396     }
397 
hasCompatibleActivityType(ConfigurationContainer other)398     public boolean hasCompatibleActivityType(ConfigurationContainer other) {
399         /*@WindowConfiguration.ActivityType*/ int thisType = getActivityType();
400         /*@WindowConfiguration.ActivityType*/ int otherType = other.getActivityType();
401 
402         if (thisType == otherType) {
403             return true;
404         }
405         if (thisType == ACTIVITY_TYPE_ASSISTANT) {
406             // Assistant activities are only compatible with themselves...
407             return false;
408         }
409         // Otherwise we are compatible if us or other is not currently defined.
410         return thisType == ACTIVITY_TYPE_UNDEFINED || otherType == ACTIVITY_TYPE_UNDEFINED;
411     }
412 
413     /**
414      * Returns true if this container is compatible with the input windowing mode and activity type.
415      * The container is compatible:
416      * - If {@param activityType} and {@param windowingMode} match this container activity type and
417      * windowing mode.
418      * - If {@param activityType} is {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} or
419      * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD} and this containers activity type is also
420      * standard or undefined and its windowing mode matches {@param windowingMode}.
421      * - If {@param activityType} isn't {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} or
422      * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD} or this containers activity type isn't
423      * also standard or undefined and its activity type matches {@param activityType} regardless of
424      * if {@param windowingMode} matches the containers windowing mode.
425      */
isCompatible(int windowingMode, int activityType)426     public boolean isCompatible(int windowingMode, int activityType) {
427         final int thisActivityType = getActivityType();
428         final int thisWindowingMode = getWindowingMode();
429         final boolean sameActivityType = thisActivityType == activityType;
430         final boolean sameWindowingMode = thisWindowingMode == windowingMode;
431 
432         if (sameActivityType && sameWindowingMode) {
433             return true;
434         }
435 
436         if ((activityType != ACTIVITY_TYPE_UNDEFINED && activityType != ACTIVITY_TYPE_STANDARD)
437                 || !isActivityTypeStandardOrUndefined()) {
438             // Only activity type need to match for non-standard activity types that are defined.
439             return sameActivityType;
440         }
441 
442         // Otherwise we are compatible if the windowing mode is the same.
443         return sameWindowingMode;
444     }
445 
registerConfigurationChangeListener(ConfigurationContainerListener listener)446     public void registerConfigurationChangeListener(ConfigurationContainerListener listener) {
447         if (mChangeListeners.contains(listener)) {
448             return;
449         }
450         mChangeListeners.add(listener);
451         listener.onOverrideConfigurationChanged(mOverrideConfiguration);
452     }
453 
unregisterConfigurationChangeListener(ConfigurationContainerListener listener)454     public void unregisterConfigurationChangeListener(ConfigurationContainerListener listener) {
455         mChangeListeners.remove(listener);
456     }
457 
458     /**
459      * Must be called when new parent for the container was set.
460      */
onParentChanged()461     protected void onParentChanged() {
462         final ConfigurationContainer parent = getParent();
463         // Removing parent usually means that we've detached this entity to destroy it or to attach
464         // to another parent. In both cases we don't need to update the configuration now.
465         if (parent != null) {
466             // Update full configuration of this container and all its children.
467             onConfigurationChanged(parent.mFullConfiguration);
468             // Update merged override configuration of this container and all its children.
469             onMergedOverrideConfigurationChanged();
470         }
471     }
472 
473     /**
474      * Write to a protocol buffer output stream. Protocol buffer message definition is at
475      * {@link com.android.server.wm.ConfigurationContainerProto}.
476      *
477      * @param proto    Stream to write the ConfigurationContainer object to.
478      * @param fieldId  Field Id of the ConfigurationContainer as defined in the parent
479      *                 message.
480      * @param trim     If true, reduce amount of data written.
481      * @hide
482      */
483     @CallSuper
writeToProto(ProtoOutputStream proto, long fieldId, boolean trim)484     public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) {
485         final long token = proto.start(fieldId);
486         if (!trim || mHasOverrideConfiguration) {
487             mOverrideConfiguration.writeToProto(proto, OVERRIDE_CONFIGURATION);
488         }
489         if (!trim) {
490             mFullConfiguration.writeToProto(proto, FULL_CONFIGURATION);
491             mMergedOverrideConfiguration.writeToProto(proto, MERGED_OVERRIDE_CONFIGURATION);
492         }
493         proto.end(token);
494     }
495 
496     /**
497      * Dumps the names of this container children in the input print writer indenting each
498      * level with the input prefix.
499      */
dumpChildrenNames(PrintWriter pw, String prefix)500     public void dumpChildrenNames(PrintWriter pw, String prefix) {
501         final String childPrefix = prefix + " ";
502         pw.println(getName()
503                 + " type=" + activityTypeToString(getActivityType())
504                 + " mode=" + windowingModeToString(getWindowingMode()));
505         for (int i = getChildCount() - 1; i >= 0; --i) {
506             final E cc = getChildAt(i);
507             pw.print(childPrefix + "#" + i + " ");
508             cc.dumpChildrenNames(pw, childPrefix);
509         }
510     }
511 
getName()512     String getName() {
513         return toString();
514     }
515 
isAlwaysOnTop()516     boolean isAlwaysOnTop() {
517         return mFullConfiguration.windowConfiguration.isAlwaysOnTop();
518     }
519 
getChildCount()520     abstract protected int getChildCount();
521 
getChildAt(int index)522     abstract protected E getChildAt(int index);
523 
getParent()524     abstract protected ConfigurationContainer getParent();
525 }
526