1 /*
2  * Copyright (C) 2020 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 androidx.window.sidecar;
18 
19 import static android.view.Display.DEFAULT_DISPLAY;
20 import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation;
21 import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect;
22 
23 import android.app.Activity;
24 import android.app.ActivityThread;
25 import android.app.Application;
26 import android.content.Context;
27 import android.graphics.Rect;
28 import android.os.Bundle;
29 import android.os.IBinder;
30 
31 import androidx.annotation.NonNull;
32 import androidx.window.common.CommonFoldingFeature;
33 import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
34 import androidx.window.common.EmptyLifecycleCallbacksAdapter;
35 import androidx.window.common.RawFoldingFeatureProducer;
36 import androidx.window.util.BaseDataProducer;
37 
38 import java.util.ArrayList;
39 import java.util.Collections;
40 import java.util.List;
41 
42 /**
43  * Reference implementation of androidx.window.sidecar OEM interface for use with
44  * WindowManager Jetpack.
45  */
46 class SampleSidecarImpl extends StubSidecar {
47     private List<CommonFoldingFeature> mStoredFeatures = new ArrayList<>();
48 
SampleSidecarImpl(Context context)49     SampleSidecarImpl(Context context) {
50         ((Application) context.getApplicationContext())
51                 .registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged());
52         BaseDataProducer<String> settingsFeatureProducer = new RawFoldingFeatureProducer(context);
53         BaseDataProducer<List<CommonFoldingFeature>> foldingFeatureProducer =
54                 new DeviceStateManagerFoldingFeatureProducer(context,
55                         settingsFeatureProducer);
56 
57         foldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
58     }
59 
setStoredFeatures(List<CommonFoldingFeature> storedFeatures)60     private void setStoredFeatures(List<CommonFoldingFeature> storedFeatures) {
61         mStoredFeatures = storedFeatures;
62     }
63 
onDisplayFeaturesChanged(List<CommonFoldingFeature> storedFeatures)64     private void onDisplayFeaturesChanged(List<CommonFoldingFeature> storedFeatures) {
65         setStoredFeatures(storedFeatures);
66         updateDeviceState(getDeviceState());
67         for (IBinder windowToken : getWindowsListeningForLayoutChanges()) {
68             SidecarWindowLayoutInfo newLayout = getWindowLayoutInfo(windowToken);
69             updateWindowLayout(windowToken, newLayout);
70         }
71     }
72 
73     @NonNull
74     @Override
getDeviceState()75     public SidecarDeviceState getDeviceState() {
76         SidecarDeviceState deviceState = new SidecarDeviceState();
77         deviceState.posture = deviceStateFromFeature();
78         return deviceState;
79     }
80 
deviceStateFromFeature()81     private int deviceStateFromFeature() {
82         for (int i = 0; i < mStoredFeatures.size(); i++) {
83             CommonFoldingFeature feature = mStoredFeatures.get(i);
84             final int state = feature.getState();
85             switch (state) {
86                 case CommonFoldingFeature.COMMON_STATE_FLAT:
87                     return SidecarDeviceState.POSTURE_OPENED;
88                 case CommonFoldingFeature.COMMON_STATE_HALF_OPENED:
89                     return SidecarDeviceState.POSTURE_HALF_OPENED;
90                 case CommonFoldingFeature.COMMON_STATE_UNKNOWN:
91                     return SidecarDeviceState.POSTURE_UNKNOWN;
92             }
93         }
94         return SidecarDeviceState.POSTURE_UNKNOWN;
95     }
96 
97     @NonNull
98     @Override
getWindowLayoutInfo(@onNull IBinder windowToken)99     public SidecarWindowLayoutInfo getWindowLayoutInfo(@NonNull IBinder windowToken) {
100         Activity activity = ActivityThread.currentActivityThread().getActivity(windowToken);
101         SidecarWindowLayoutInfo windowLayoutInfo = new SidecarWindowLayoutInfo();
102         if (activity == null) {
103             return windowLayoutInfo;
104         }
105         windowLayoutInfo.displayFeatures = getDisplayFeatures(activity);
106         return windowLayoutInfo;
107     }
108 
getDisplayFeatures(@onNull Activity activity)109     private List<SidecarDisplayFeature> getDisplayFeatures(@NonNull Activity activity) {
110         int displayId = activity.getDisplay().getDisplayId();
111         if (displayId != DEFAULT_DISPLAY) {
112             return Collections.emptyList();
113         }
114 
115         if (activity.isInMultiWindowMode()) {
116             // It is recommended not to report any display features in multi-window mode, since it
117             // won't be possible to synchronize the display feature positions with window movement.
118             return Collections.emptyList();
119         }
120 
121         List<SidecarDisplayFeature> features = new ArrayList<>();
122         final int rotation = activity.getResources().getConfiguration().windowConfiguration
123                 .getDisplayRotation();
124         for (CommonFoldingFeature baseFeature : mStoredFeatures) {
125             SidecarDisplayFeature feature = new SidecarDisplayFeature();
126             Rect featureRect = baseFeature.getRect();
127             rotateRectToDisplayRotation(displayId, rotation, featureRect);
128             transformToWindowSpaceRect(activity, featureRect);
129             feature.setRect(featureRect);
130             feature.setType(baseFeature.getType());
131             features.add(feature);
132         }
133         return Collections.unmodifiableList(features);
134     }
135 
136     @Override
onListenersChanged()137     protected void onListenersChanged() {
138         if (hasListeners()) {
139             onDisplayFeaturesChanged(mStoredFeatures);
140         }
141     }
142 
143     private final class NotifyOnConfigurationChanged extends EmptyLifecycleCallbacksAdapter {
144         @Override
onActivityCreated(Activity activity, Bundle savedInstanceState)145         public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
146             super.onActivityCreated(activity, savedInstanceState);
147             onDisplayFeaturesChangedForActivity(activity);
148         }
149 
150         @Override
onActivityConfigurationChanged(Activity activity)151         public void onActivityConfigurationChanged(Activity activity) {
152             super.onActivityConfigurationChanged(activity);
153             onDisplayFeaturesChangedForActivity(activity);
154         }
155 
onDisplayFeaturesChangedForActivity(@onNull Activity activity)156         private void onDisplayFeaturesChangedForActivity(@NonNull Activity activity) {
157             IBinder token = activity.getWindow().getAttributes().token;
158             if (token == null || mWindowLayoutChangeListenerTokens.contains(token)) {
159                 onDisplayFeaturesChanged(mStoredFeatures);
160             }
161         }
162     }
163 }
164