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.Nullable; 24 25 import com.android.car.qc.controller.BaseQCController; 26 import com.android.car.qc.controller.LocalQCController; 27 import com.android.car.qc.controller.RemoteQCController; 28 import com.android.car.qc.provider.BaseLocalQCProvider; 29 import com.android.systemui.settings.UserTracker; 30 31 import java.lang.reflect.Constructor; 32 import java.util.Map; 33 34 import javax.inject.Inject; 35 import javax.inject.Provider; 36 37 /** 38 * Class to control instances of {@link SystemUIQCView}. This controller is responsible for 39 * attaching either a remote controller for the current user or a local controller using a dagger 40 * injected constructor and fall back to a default {@link Context} constructor when not present. 41 */ 42 public final class SystemUIQCViewController { 43 private static final String TAG = SystemUIQCViewController.class.getName(); 44 private final Context mContext; 45 private final UserTracker mUserTracker; 46 private final Map<Class<?>, Provider<BaseLocalQCProvider>> mLocalQCProviderCreators; 47 private SystemUIQCView mView; 48 private BaseQCController mController; 49 private boolean mUserChangedCallbackRegistered; 50 51 private final UserTracker.Callback mUserChangedCallback = new UserTracker.Callback() { 52 @Override 53 public void onUserChanged(int newUser, Context userContext) { 54 rebindController(); 55 } 56 }; 57 58 @Inject SystemUIQCViewController(Context context, UserTracker userTracker, Map<Class<?>, Provider<BaseLocalQCProvider>> localQCProviderCreators)59 public SystemUIQCViewController(Context context, UserTracker userTracker, 60 Map<Class<?>, Provider<BaseLocalQCProvider>> localQCProviderCreators) { 61 mContext = context; 62 mUserTracker = userTracker; 63 mLocalQCProviderCreators = localQCProviderCreators; 64 } 65 66 /** 67 * Attaches a {@link SystemUIQCView} to this controller. 68 */ attachView(SystemUIQCView view)69 public void attachView(SystemUIQCView view) { 70 mView = view; 71 if (mView.getRemoteUriString() != null) { 72 Uri uri = Uri.parse(mView.getRemoteUriString()); 73 if (uri.getUserInfo() == null) { 74 // To bind to the content provider as the current user rather than user 0 (which 75 // SystemUI is running on), add the current user id followed by the '@' symbol 76 // before the Uri's authority. 77 uri = uri.buildUpon().authority( 78 String.format("%s@%s", mUserTracker.getUserId(), 79 uri.getAuthority())).build(); 80 } 81 bindRemoteQCView(uri); 82 if (!mUserChangedCallbackRegistered) { 83 mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor()); 84 mUserChangedCallbackRegistered = true; 85 } 86 } else if (mView.getLocalClassString() != null) { 87 bindLocalQCView(mView.getLocalClassString()); 88 } 89 } 90 91 /** 92 * Toggles whether or not this view should listen to live updates. 93 */ listen(boolean shouldListen)94 public void listen(boolean shouldListen) { 95 if (mController != null) { 96 mController.listen(shouldListen); 97 } 98 } 99 100 /** 101 * Destroys the current QCView and associated controller. 102 */ destroy()103 public void destroy() { 104 resetViewAndController(); 105 if (mUserChangedCallbackRegistered) { 106 mUserTracker.removeCallback(mUserChangedCallback); 107 mUserChangedCallbackRegistered = false; 108 } 109 } 110 rebindController()111 private void rebindController() { 112 if (mView == null) { 113 return; 114 } 115 resetViewAndController(); 116 attachView(mView); 117 } 118 resetViewAndController()119 private void resetViewAndController() { 120 if (mController != null) { 121 mController.destroy(); 122 mController = null; 123 } 124 if (mView != null) { 125 mView.onChanged(/* qcItem= */ null); 126 } 127 } 128 bindRemoteQCView(Uri uri)129 private void bindRemoteQCView(Uri uri) { 130 mController = new RemoteQCController(mContext, uri); 131 mController.addObserver(mView); 132 mController.bind(); 133 } 134 bindLocalQCView(String localClass)135 private void bindLocalQCView(String localClass) { 136 BaseLocalQCProvider localQCProvider = createLocalQCProviderInstance(localClass, mContext); 137 mController = new LocalQCController(mContext, localQCProvider); 138 mController.addObserver(mView); 139 mController.bind(); 140 } 141 createLocalQCProviderInstance(String controllerName, Context context)142 private BaseLocalQCProvider createLocalQCProviderInstance(String controllerName, 143 Context context) { 144 try { 145 BaseLocalQCProvider injectedProvider = resolveInjectedLocalQCProviderInstance( 146 controllerName); 147 if (injectedProvider != null) { 148 return injectedProvider; 149 } 150 Class<?> clazz = Class.forName(controllerName); 151 Constructor<?> providerConstructor = clazz.getConstructor(Context.class); 152 return (BaseLocalQCProvider) providerConstructor.newInstance(context); 153 } catch (ReflectiveOperationException e) { 154 throw new IllegalArgumentException("Invalid controller: " + controllerName, e); 155 } 156 } 157 resolveInjectedLocalQCProviderInstance(@ullable String className)158 private BaseLocalQCProvider resolveInjectedLocalQCProviderInstance(@Nullable String className) { 159 try { 160 Class<?> clazz = Class.forName(className); 161 Provider<BaseLocalQCProvider> provider = mLocalQCProviderCreators.get(clazz); 162 return provider == null ? null : provider.get(); 163 } catch (ClassNotFoundException e) { 164 Log.d(TAG, "Could not find class " + className); 165 return null; 166 } 167 } 168 } 169