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