1 /* 2 * Copyright (C) 2022 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.systemui.car.qc; 18 19 import android.content.Context; 20 import android.net.Uri; 21 import android.util.Log; 22 23 import androidx.annotation.CallSuper; 24 import androidx.annotation.Nullable; 25 26 import com.android.car.qc.controller.BaseQCController; 27 import com.android.car.qc.controller.LocalQCController; 28 import com.android.car.qc.controller.RemoteQCController; 29 import com.android.car.qc.provider.BaseLocalQCProvider; 30 import com.android.car.qc.view.QCView; 31 import com.android.systemui.car.systembar.element.CarSystemBarElementController; 32 import com.android.systemui.car.systembar.element.CarSystemBarElementStateController; 33 import com.android.systemui.car.systembar.element.CarSystemBarElementStatusBarDisableController; 34 import com.android.systemui.settings.UserTracker; 35 36 import dagger.assisted.Assisted; 37 import dagger.assisted.AssistedFactory; 38 import dagger.assisted.AssistedInject; 39 40 import java.lang.reflect.Constructor; 41 import java.util.Map; 42 43 import javax.inject.Provider; 44 45 /** 46 * Class to control instances of {@link SystemUIQCView}. This controller is responsible for 47 * attaching either a remote controller for the current user or a local controller using a dagger 48 * injected constructor and fall back to a default {@link Context} constructor when not present. 49 */ 50 public final class SystemUIQCViewController extends CarSystemBarElementController<SystemUIQCView> { 51 private static final String TAG = SystemUIQCViewController.class.getName(); 52 private final Context mContext; 53 private final UserTracker mUserTracker; 54 private final Map<Class<?>, Provider<BaseLocalQCProvider>> mLocalQCProviderCreators; 55 private BaseQCController mController; 56 private boolean mUserChangedCallbackRegistered; 57 58 private final UserTracker.Callback mUserChangedCallback = new UserTracker.Callback() { 59 @Override 60 public void onUserChanged(int newUser, Context userContext) { 61 rebindController(); 62 } 63 }; 64 65 @AssistedInject SystemUIQCViewController(@ssisted SystemUIQCView view, CarSystemBarElementStatusBarDisableController disableController, CarSystemBarElementStateController stateController, Context context, UserTracker userTracker, Map<Class<?>, Provider<BaseLocalQCProvider>> localQCProviderCreators)66 public SystemUIQCViewController(@Assisted SystemUIQCView view, 67 CarSystemBarElementStatusBarDisableController disableController, 68 CarSystemBarElementStateController stateController, Context context, 69 UserTracker userTracker, 70 Map<Class<?>, Provider<BaseLocalQCProvider>> localQCProviderCreators) { 71 super(view, disableController, stateController); 72 mContext = context; 73 mUserTracker = userTracker; 74 mLocalQCProviderCreators = localQCProviderCreators; 75 } 76 77 @AssistedFactory 78 public interface Factory extends 79 CarSystemBarElementController.Factory<SystemUIQCView, SystemUIQCViewController> {} 80 81 @Override onInit()82 protected void onInit() { 83 bindQCView(); 84 } 85 86 @Override 87 @CallSuper onViewAttached()88 protected void onViewAttached() { 89 super.onViewAttached(); 90 listen(true); 91 } 92 93 @Override 94 @CallSuper onViewDetached()95 protected void onViewDetached() { 96 super.onViewDetached(); 97 listen(false); 98 } 99 100 /** 101 * Sets the action listener on the QCView. 102 */ setActionListener(QCView.QCActionListener listener)103 public void setActionListener(QCView.QCActionListener listener) { 104 mView.setActionListener(listener); 105 } 106 107 /** 108 * Destroys the current QCView and associated controller. Should be called when the anchor 109 * view is no longer attached. 110 */ destroyQCViews()111 public void destroyQCViews() { 112 resetViewAndController(); 113 if (mUserChangedCallbackRegistered) { 114 mUserTracker.removeCallback(mUserChangedCallback); 115 mUserChangedCallbackRegistered = false; 116 } 117 } 118 bindQCView()119 private void bindQCView() { 120 if (mView.getRemoteUriString() != null) { 121 Uri uri = Uri.parse(mView.getRemoteUriString()); 122 if (uri.getUserInfo() == null) { 123 // To bind to the content provider as the current user rather than user 0 (which 124 // SystemUI is running on), add the current user id followed by the '@' symbol 125 // before the Uri's authority. 126 uri = uri.buildUpon().authority( 127 String.format("%s@%s", mUserTracker.getUserId(), 128 uri.getAuthority())).build(); 129 } 130 bindRemoteQCView(uri); 131 if (!mUserChangedCallbackRegistered) { 132 mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor()); 133 mUserChangedCallbackRegistered = true; 134 } 135 } else if (mView.getLocalClassString() != null) { 136 bindLocalQCView(mView.getLocalClassString()); 137 } 138 } 139 140 /** 141 * Toggles whether this view should listen to live updates. 142 */ listen(boolean shouldListen)143 private void listen(boolean shouldListen) { 144 if (mController != null) { 145 mController.listen(shouldListen); 146 } 147 } 148 rebindController()149 private void rebindController() { 150 resetViewAndController(); 151 bindQCView(); 152 } 153 resetViewAndController()154 private void resetViewAndController() { 155 if (mController != null) { 156 mController.destroy(); 157 mController = null; 158 } 159 if (mView != null) { 160 mView.onChanged(/* qcItem= */ null); 161 } 162 } 163 bindRemoteQCView(Uri uri)164 private void bindRemoteQCView(Uri uri) { 165 mController = new RemoteQCController(mContext, uri); 166 mController.addObserver(mView); 167 mController.bind(); 168 } 169 bindLocalQCView(String localClass)170 private void bindLocalQCView(String localClass) { 171 BaseLocalQCProvider localQCProvider = createLocalQCProviderInstance(localClass, mContext); 172 mController = new LocalQCController(mContext, localQCProvider); 173 mController.addObserver(mView); 174 mController.bind(); 175 } 176 createLocalQCProviderInstance(String controllerName, Context context)177 private BaseLocalQCProvider createLocalQCProviderInstance(String controllerName, 178 Context context) { 179 try { 180 BaseLocalQCProvider injectedProvider = resolveInjectedLocalQCProviderInstance( 181 controllerName); 182 if (injectedProvider != null) { 183 return injectedProvider; 184 } 185 Class<?> clazz = Class.forName(controllerName); 186 Constructor<?> providerConstructor = clazz.getConstructor(Context.class); 187 return (BaseLocalQCProvider) providerConstructor.newInstance(context); 188 } catch (ReflectiveOperationException e) { 189 throw new IllegalArgumentException("Invalid controller: " + controllerName, e); 190 } 191 } 192 resolveInjectedLocalQCProviderInstance(@ullable String className)193 private BaseLocalQCProvider resolveInjectedLocalQCProviderInstance(@Nullable String className) { 194 try { 195 Class<?> clazz = Class.forName(className); 196 Provider<BaseLocalQCProvider> provider = mLocalQCProviderCreators.get(clazz); 197 return provider == null ? null : provider.get(); 198 } catch (ClassNotFoundException e) { 199 Log.d(TAG, "Could not find class " + className); 200 return null; 201 } 202 } 203 } 204