1 /*
2  * Copyright (C) 2021 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.app.ActivityManager;
20 import android.content.Context;
21 import android.content.res.TypedArray;
22 import android.net.Uri;
23 import android.util.AttributeSet;
24 
25 import androidx.annotation.NonNull;
26 import androidx.annotation.Nullable;
27 
28 import com.android.car.qc.controller.BaseQCController;
29 import com.android.car.qc.controller.LocalQCController;
30 import com.android.car.qc.controller.RemoteQCController;
31 import com.android.car.qc.provider.BaseLocalQCProvider;
32 import com.android.car.qc.view.QCView;
33 import com.android.systemui.R;
34 
35 import java.lang.reflect.Constructor;
36 import java.lang.reflect.InvocationTargetException;
37 
38 /**
39  * Quick Control View Element for CarSystemUI.
40  *
41  * This extended class allows for specifying a local or remote quick controls provider via xml
42  * attributes. When a remote provider is specified, it will bind to the provider as the current
43  * foreground user instead of user 0.
44  *
45  * @attr ref R.styleable#SystemUIQCView_remoteQCProvider
46  * @attr ref R.styleable#SystemUIQCView_localQCProvider
47  */
48 public class SystemUIQCView extends QCView {
49     private BaseQCController mController;
50     private BaseLocalQCProvider mLocalQCProvider;
51 
SystemUIQCView(Context context)52     public SystemUIQCView(Context context) {
53         super(context);
54         init(context, /* attrs= */ null);
55     }
56 
SystemUIQCView(Context context, AttributeSet attrs)57     public SystemUIQCView(Context context, AttributeSet attrs) {
58         super(context, attrs);
59         init(context, attrs);
60     }
61 
SystemUIQCView(Context context, AttributeSet attrs, int defStyleAttr)62     public SystemUIQCView(Context context, AttributeSet attrs, int defStyleAttr) {
63         super(context, attrs, defStyleAttr);
64         init(context, attrs);
65     }
66 
SystemUIQCView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)67     public SystemUIQCView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
68         super(context, attrs, defStyleAttr, defStyleRes);
69         init(context, attrs);
70     }
71 
72     /**
73      * Toggle whether or not this view should listen to live updates.
74      */
listen(boolean shouldListen)75     public void listen(boolean shouldListen) {
76         if (mController != null) {
77             mController.listen(shouldListen);
78         }
79     }
80 
81     /**
82      * Destroy the current QCView and associated controller.
83      */
destroy()84     public void destroy() {
85         if (mController != null) {
86             mController.destroy();
87             mController = null;
88         }
89         removeAllViews();
90     }
91 
92     /**
93      * @return {@link BaseLocalQCProvider} for this System UI Quick Controls View.
94      */
95     @Nullable
getLocalQCProvider()96     public BaseLocalQCProvider getLocalQCProvider() {
97         return mLocalQCProvider;
98     }
99 
init(@onNull Context context, @Nullable AttributeSet attrs)100     private void init(@NonNull Context context, @Nullable AttributeSet attrs) {
101         setFocusable(false);
102         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SystemUIQCView);
103         String remoteUri = a.getString(R.styleable.SystemUIQCView_remoteQCProvider);
104         if (remoteUri != null) {
105             Uri uri = Uri.parse(remoteUri);
106             if (uri.getUserInfo() == null) {
107                 // To bind to the content provider as the current user rather than user 0 (which
108                 // SystemUI is running on), add the current user id followed by the '@' symbol
109                 // before the Uri's authority.
110                 uri = uri.buildUpon().authority(
111                         String.format("%s@%s", ActivityManager.getCurrentUser(),
112                                 uri.getAuthority())).build();
113             }
114             bindRemoteQCView(uri);
115         } else {
116             String localClass = a.getString(R.styleable.SystemUIQCView_localQCProvider);
117             if (localClass != null) {
118                 bindLocalQCView(localClass);
119             }
120         }
121         a.recycle();
122     }
123 
bindRemoteQCView(Uri uri)124     private void bindRemoteQCView(Uri uri) {
125         mController = new RemoteQCController(mContext, uri);
126         mController.addObserver(this);
127         mController.listen(true);
128     }
129 
bindLocalQCView(String localClass)130     private void bindLocalQCView(String localClass) {
131         mLocalQCProvider = createLocalQCProviderInstance(localClass, mContext);
132         mController = new LocalQCController(mContext, mLocalQCProvider);
133         mController.addObserver(this);
134         mController.listen(true);
135     }
136 
createLocalQCProviderInstance(String controllerName, Context context)137     private BaseLocalQCProvider createLocalQCProviderInstance(String controllerName,
138             Context context) {
139         try {
140             Class<?> clazz = Class.forName(controllerName);
141             Constructor<?> providerConstructor = clazz.getConstructor(Context.class);
142             return (BaseLocalQCProvider) providerConstructor.newInstance(context);
143         } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException
144                 | InvocationTargetException | IllegalAccessException e) {
145             throw new IllegalArgumentException("Invalid controller: " + controllerName, e);
146         }
147     }
148 }
149