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