• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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 android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.app.Activity;
22 import android.app.Application;
23 import android.content.Context;
24 import android.hardware.devicestate.DeviceStateManager;
25 import android.os.Bundle;
26 import android.os.IBinder;
27 import android.util.ArraySet;
28 import android.util.Log;
29 
30 import androidx.window.common.BaseDataProducer;
31 import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
32 import androidx.window.common.EmptyLifecycleCallbacksAdapter;
33 import androidx.window.common.RawFoldingFeatureProducer;
34 import androidx.window.common.layout.CommonFoldingFeature;
35 
36 import java.util.ArrayList;
37 import java.util.List;
38 import java.util.Objects;
39 import java.util.Set;
40 
41 /**
42  * Basic implementation of the {@link SidecarInterface}. An OEM can choose to use it as the base
43  * class for their implementation.
44  */
45 class SidecarImpl implements SidecarInterface {
46 
47     private static final String TAG = "WindowManagerSidecar";
48 
49     @Nullable
50     private SidecarCallback mSidecarCallback;
51     private final ArraySet<IBinder> mWindowLayoutChangeListenerTokens = new ArraySet<>();
52     private boolean mDeviceStateChangeListenerRegistered;
53     @NonNull
54     private List<CommonFoldingFeature> mStoredFeatures = new ArrayList<>();
55 
SidecarImpl(Context context)56     SidecarImpl(Context context) {
57         ((Application) context.getApplicationContext())
58                 .registerActivityLifecycleCallbacks(new SidecarImpl.NotifyOnConfigurationChanged());
59         RawFoldingFeatureProducer settingsFeatureProducer = new RawFoldingFeatureProducer(context);
60         BaseDataProducer<List<CommonFoldingFeature>> foldingFeatureProducer =
61                 new DeviceStateManagerFoldingFeatureProducer(context,
62                         settingsFeatureProducer,
63                         context.getSystemService(DeviceStateManager.class));
64 
65         foldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
66     }
67 
68     @NonNull
69     @Override
getDeviceState()70     public SidecarDeviceState getDeviceState() {
71         return SidecarHelper.calculateDeviceState(mStoredFeatures);
72     }
73 
74     @NonNull
75     @Override
getWindowLayoutInfo(@onNull IBinder windowToken)76     public SidecarWindowLayoutInfo getWindowLayoutInfo(@NonNull IBinder windowToken) {
77         return SidecarHelper.calculateWindowLayoutInfo(windowToken, mStoredFeatures);
78     }
79 
80     @Override
setSidecarCallback(@onNull SidecarCallback sidecarCallback)81     public void setSidecarCallback(@NonNull SidecarCallback sidecarCallback) {
82         mSidecarCallback = sidecarCallback;
83     }
84 
85     @Override
onWindowLayoutChangeListenerAdded(@onNull IBinder iBinder)86     public void onWindowLayoutChangeListenerAdded(@NonNull IBinder iBinder) {
87         mWindowLayoutChangeListenerTokens.add(iBinder);
88         onListenersChanged();
89     }
90 
91     @Override
onWindowLayoutChangeListenerRemoved(@onNull IBinder iBinder)92     public void onWindowLayoutChangeListenerRemoved(@NonNull IBinder iBinder) {
93         mWindowLayoutChangeListenerTokens.remove(iBinder);
94         onListenersChanged();
95     }
96 
97     @Override
onDeviceStateListenersChanged(boolean isEmpty)98     public void onDeviceStateListenersChanged(boolean isEmpty) {
99         mDeviceStateChangeListenerRegistered = !isEmpty;
100         onListenersChanged();
101     }
102 
setStoredFeatures(@onNull List<CommonFoldingFeature> storedFeatures)103     private void setStoredFeatures(@NonNull List<CommonFoldingFeature> storedFeatures) {
104         mStoredFeatures = Objects.requireNonNull(storedFeatures);
105     }
106 
onDisplayFeaturesChanged(@onNull List<CommonFoldingFeature> storedFeatures)107     private void onDisplayFeaturesChanged(@NonNull List<CommonFoldingFeature> storedFeatures) {
108         setStoredFeatures(storedFeatures);
109         updateDeviceState(getDeviceState());
110         for (IBinder windowToken : getWindowsListeningForLayoutChanges()) {
111             SidecarWindowLayoutInfo newLayout = getWindowLayoutInfo(windowToken);
112             updateWindowLayout(windowToken, newLayout);
113         }
114     }
115 
updateDeviceState(@onNull SidecarDeviceState newState)116     void updateDeviceState(@NonNull SidecarDeviceState newState) {
117         if (mSidecarCallback != null) {
118             try {
119                 mSidecarCallback.onDeviceStateChanged(newState);
120             } catch (AbstractMethodError e) {
121                 Log.e(TAG, "App is using an outdated Window Jetpack library", e);
122             }
123         }
124     }
125 
updateWindowLayout(@onNull IBinder windowToken, @NonNull SidecarWindowLayoutInfo newLayout)126     void updateWindowLayout(@NonNull IBinder windowToken,
127             @NonNull SidecarWindowLayoutInfo newLayout) {
128         if (mSidecarCallback != null) {
129             try {
130                 mSidecarCallback.onWindowLayoutChanged(windowToken, newLayout);
131             } catch (AbstractMethodError e) {
132                 Log.e(TAG, "App is using an outdated Window Jetpack library", e);
133             }
134         }
135     }
136 
137     @NonNull
getWindowsListeningForLayoutChanges()138     private Set<IBinder> getWindowsListeningForLayoutChanges() {
139         return mWindowLayoutChangeListenerTokens;
140     }
141 
hasListeners()142     protected boolean hasListeners() {
143         return !mWindowLayoutChangeListenerTokens.isEmpty() || mDeviceStateChangeListenerRegistered;
144     }
145 
onListenersChanged()146     private void onListenersChanged() {
147         if (hasListeners()) {
148             onDisplayFeaturesChanged(mStoredFeatures);
149         }
150     }
151 
152 
153     private final class NotifyOnConfigurationChanged extends EmptyLifecycleCallbacksAdapter {
154         @Override
onActivityCreated(@onNull Activity activity, @Nullable Bundle savedInstanceState)155         public void onActivityCreated(@NonNull Activity activity,
156                 @Nullable Bundle savedInstanceState) {
157             super.onActivityCreated(activity, savedInstanceState);
158             onDisplayFeaturesChangedForActivity(activity);
159         }
160 
161         @Override
onActivityConfigurationChanged(@onNull Activity activity)162         public void onActivityConfigurationChanged(@NonNull Activity activity) {
163             super.onActivityConfigurationChanged(activity);
164             onDisplayFeaturesChangedForActivity(activity);
165         }
166 
onDisplayFeaturesChangedForActivity(@onNull Activity activity)167         private void onDisplayFeaturesChangedForActivity(@NonNull Activity activity) {
168             IBinder token = activity.getWindow().getAttributes().token;
169             if (token == null || mWindowLayoutChangeListenerTokens.contains(token)) {
170                 onDisplayFeaturesChanged(mStoredFeatures);
171             }
172         }
173     }
174 }
175