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 
21 import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation;
22 import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect;
23 
24 import android.app.Activity;
25 import android.app.ActivityThread;
26 import android.content.Context;
27 import android.graphics.Rect;
28 import android.os.IBinder;
29 import android.util.Log;
30 
31 import androidx.annotation.NonNull;
32 import androidx.window.common.DeviceStateManagerPostureProducer;
33 import androidx.window.common.DisplayFeature;
34 import androidx.window.common.ResourceConfigDisplayFeatureProducer;
35 import androidx.window.common.SettingsDevicePostureProducer;
36 import androidx.window.common.SettingsDisplayFeatureProducer;
37 import androidx.window.util.DataProducer;
38 import androidx.window.util.PriorityDataProducer;
39 
40 import java.util.ArrayList;
41 import java.util.Collections;
42 import java.util.List;
43 import java.util.Optional;
44 
45 /**
46  * Reference implementation of androidx.window.sidecar OEM interface for use with
47  * WindowManager Jetpack.
48  */
49 class SampleSidecarImpl extends StubSidecar {
50     private static final String TAG = "SampleSidecar";
51     private static final boolean DEBUG = false;
52 
53     private final SettingsDevicePostureProducer mSettingsDevicePostureProducer;
54     private final DataProducer<Integer> mDevicePostureProducer;
55 
56     private final SettingsDisplayFeatureProducer mSettingsDisplayFeatureProducer;
57     private final DataProducer<List<DisplayFeature>> mDisplayFeatureProducer;
58 
SampleSidecarImpl(Context context)59     SampleSidecarImpl(Context context) {
60         mSettingsDevicePostureProducer = new SettingsDevicePostureProducer(context);
61         mDevicePostureProducer = new PriorityDataProducer<>(List.of(
62                 mSettingsDevicePostureProducer,
63                 new DeviceStateManagerPostureProducer(context)
64         ));
65 
66         mSettingsDisplayFeatureProducer = new SettingsDisplayFeatureProducer(context);
67         mDisplayFeatureProducer = new PriorityDataProducer<>(List.of(
68                 mSettingsDisplayFeatureProducer,
69                 new ResourceConfigDisplayFeatureProducer(context)
70         ));
71 
72         mDevicePostureProducer.addDataChangedCallback(this::onDevicePostureChanged);
73         mDisplayFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
74     }
75 
onDevicePostureChanged()76     private void onDevicePostureChanged() {
77         updateDeviceState(getDeviceState());
78     }
79 
onDisplayFeaturesChanged()80     private void onDisplayFeaturesChanged() {
81         for (IBinder windowToken : getWindowsListeningForLayoutChanges()) {
82             SidecarWindowLayoutInfo newLayout = getWindowLayoutInfo(windowToken);
83             updateWindowLayout(windowToken, newLayout);
84         }
85     }
86 
87     @NonNull
88     @Override
getDeviceState()89     public SidecarDeviceState getDeviceState() {
90         Optional<Integer> posture = mDevicePostureProducer.getData();
91 
92         SidecarDeviceState deviceState = new SidecarDeviceState();
93         deviceState.posture = posture.orElse(deviceStateFromFeature());
94         return deviceState;
95     }
96 
deviceStateFromFeature()97     private int deviceStateFromFeature() {
98         List<DisplayFeature> storedFeatures = mDisplayFeatureProducer.getData()
99                 .orElse(Collections.emptyList());
100         for (int i = 0; i < storedFeatures.size(); i++) {
101             DisplayFeature feature = storedFeatures.get(i);
102             final int state = feature.getState() == null ? -1 : feature.getState();
103             if (DEBUG && feature.getState() == null) {
104                 Log.d(TAG, "feature#getState was null for DisplayFeature: " + feature);
105             }
106 
107             switch (state) {
108                 case DisplayFeature.COMMON_STATE_FLAT:
109                     return SidecarDeviceState.POSTURE_OPENED;
110                 case DisplayFeature.COMMON_STATE_HALF_OPENED:
111                     return SidecarDeviceState.POSTURE_HALF_OPENED;
112             }
113         }
114         return SidecarDeviceState.POSTURE_UNKNOWN;
115     }
116 
117     @NonNull
118     @Override
getWindowLayoutInfo(@onNull IBinder windowToken)119     public SidecarWindowLayoutInfo getWindowLayoutInfo(@NonNull IBinder windowToken) {
120         Activity activity = ActivityThread.currentActivityThread().getActivity(windowToken);
121         SidecarWindowLayoutInfo windowLayoutInfo = new SidecarWindowLayoutInfo();
122         if (activity == null) {
123             return windowLayoutInfo;
124         }
125         windowLayoutInfo.displayFeatures = getDisplayFeatures(activity);
126         return windowLayoutInfo;
127     }
128 
getDisplayFeatures(@onNull Activity activity)129     private List<SidecarDisplayFeature> getDisplayFeatures(@NonNull Activity activity) {
130         List<SidecarDisplayFeature> features = new ArrayList<SidecarDisplayFeature>();
131         int displayId = activity.getDisplay().getDisplayId();
132         if (displayId != DEFAULT_DISPLAY) {
133             Log.w(TAG, "This sample doesn't support display features on secondary displays");
134             return features;
135         }
136 
137         if (activity.isInMultiWindowMode()) {
138             // It is recommended not to report any display features in multi-window mode, since it
139             // won't be possible to synchronize the display feature positions with window movement.
140             return features;
141         }
142 
143         Optional<List<DisplayFeature>> storedFeatures = mDisplayFeatureProducer.getData();
144         if (storedFeatures.isPresent()) {
145             for (DisplayFeature baseFeature : storedFeatures.get()) {
146                 SidecarDisplayFeature feature = new SidecarDisplayFeature();
147                 Rect featureRect = baseFeature.getRect();
148                 rotateRectToDisplayRotation(displayId, featureRect);
149                 transformToWindowSpaceRect(activity, featureRect);
150                 feature.setRect(featureRect);
151                 feature.setType(baseFeature.getType());
152                 features.add(feature);
153             }
154         }
155         return features;
156     }
157 
158     @Override
onListenersChanged()159     protected void onListenersChanged() {
160         if (hasListeners()) {
161             mSettingsDevicePostureProducer.registerObserversIfNeeded();
162             mSettingsDisplayFeatureProducer.registerObserversIfNeeded();
163         } else {
164             mSettingsDevicePostureProducer.unregisterObserversIfNeeded();
165             mSettingsDisplayFeatureProducer.unregisterObserversIfNeeded();
166         }
167     }
168 }
169