1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11  * KIND, either express or implied. See the License for the specific language governing
12  * permissions and limitations under the License.
13  */
14 
15 package com.android.systemui.tuner;
16 
17 import android.content.BroadcastReceiver;
18 import android.content.ComponentName;
19 import android.content.Context;
20 import android.content.Intent;
21 import android.content.IntentFilter;
22 import android.content.pm.PackageInfo;
23 import android.content.pm.PackageManager;
24 import android.content.pm.ResolveInfo;
25 import android.net.Uri;
26 import android.os.Bundle;
27 import android.provider.Settings;
28 import android.util.ArrayMap;
29 import android.util.ArraySet;
30 import android.view.View;
31 
32 import androidx.preference.PreferenceFragment;
33 import androidx.preference.PreferenceScreen;
34 import androidx.preference.PreferenceViewHolder;
35 import androidx.preference.SwitchPreference;
36 
37 import com.android.internal.util.ArrayUtils;
38 import com.android.systemui.Dependency;
39 import com.android.systemui.R;
40 import com.android.systemui.plugins.PluginEnablerImpl;
41 import com.android.systemui.shared.plugins.PluginEnabler;
42 import com.android.systemui.shared.plugins.PluginInstanceManager;
43 import com.android.systemui.shared.plugins.PluginManager;
44 import com.android.systemui.shared.plugins.PluginPrefs;
45 
46 import java.util.List;
47 import java.util.Set;
48 
49 public class PluginFragment extends PreferenceFragment {
50 
51     public static final String ACTION_PLUGIN_SETTINGS
52             = "com.android.systemui.action.PLUGIN_SETTINGS";
53 
54     private PluginPrefs mPluginPrefs;
55     private PluginEnabler mPluginEnabler;
56 
57     @Override
onCreate(Bundle savedInstanceState)58     public void onCreate(Bundle savedInstanceState) {
59         super.onCreate(savedInstanceState);
60         IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
61         filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
62         filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
63         filter.addDataScheme("package");
64         getContext().registerReceiver(mReceiver, filter);
65         filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
66         getContext().registerReceiver(mReceiver, filter);
67     }
68 
69     @Override
onDestroy()70     public void onDestroy() {
71         super.onDestroy();
72         getContext().unregisterReceiver(mReceiver);
73     }
74 
75     @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)76     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
77         mPluginEnabler = new PluginEnablerImpl(getContext());
78         loadPrefs();
79     }
80 
loadPrefs()81     private void loadPrefs() {
82         PluginManager manager = Dependency.get(PluginManager.class);
83         PreferenceScreen screen = getPreferenceManager().createPreferenceScreen(getContext());
84         screen.setOrderingAsAdded(false);
85         Context prefContext = getPreferenceManager().getContext();
86         mPluginPrefs = new PluginPrefs(getContext());
87         PackageManager pm = getContext().getPackageManager();
88 
89         Set<String> pluginActions = mPluginPrefs.getPluginList();
90         ArrayMap<String, ArraySet<String>> plugins = new ArrayMap<>();
91         for (String action : pluginActions) {
92             String name = toName(action);
93             List<ResolveInfo> result = pm.queryIntentServices(
94                     new Intent(action), PackageManager.MATCH_DISABLED_COMPONENTS);
95             for (ResolveInfo info : result) {
96                 String packageName = info.serviceInfo.packageName;
97                 if (!plugins.containsKey(packageName)) {
98                     plugins.put(packageName, new ArraySet<>());
99                 }
100                 plugins.get(packageName).add(name);
101             }
102         }
103 
104         List<PackageInfo> apps = pm.getPackagesHoldingPermissions(new String[]{
105                 PluginInstanceManager.PLUGIN_PERMISSION},
106                 PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.GET_SERVICES);
107         apps.forEach(app -> {
108             if (!plugins.containsKey(app.packageName)) return;
109             if (ArrayUtils.contains(manager.getWhitelistedPlugins(), app.packageName)) {
110                 // Don't manage whitelisted plugins, they are part of the OS.
111                 return;
112             }
113             SwitchPreference pref = new PluginPreference(prefContext, app, mPluginEnabler);
114             pref.setSummary("Plugins: " + toString(plugins.get(app.packageName)));
115             screen.addPreference(pref);
116         });
117         setPreferenceScreen(screen);
118     }
119 
toString(ArraySet<String> plugins)120     private String toString(ArraySet<String> plugins) {
121         StringBuilder b = new StringBuilder();
122         for (String string : plugins) {
123             if (b.length() != 0) {
124                 b.append(", ");
125             }
126             b.append(string);
127         }
128         return b.toString();
129     }
130 
toName(String action)131     private String toName(String action) {
132         String str = action.replace("com.android.systemui.action.PLUGIN_", "");
133         StringBuilder b = new StringBuilder();
134         for (String s : str.split("_")) {
135             if (b.length() != 0) {
136                 b.append(' ');
137             }
138             b.append(s.substring(0, 1));
139             b.append(s.substring(1).toLowerCase());
140         }
141         return b.toString();
142     }
143 
144     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
145         @Override
146         public void onReceive(Context context, Intent intent) {
147             loadPrefs();
148         }
149     };
150 
151     private static class PluginPreference extends SwitchPreference {
152         private final boolean mHasSettings;
153         private final PackageInfo mInfo;
154         private final PluginEnabler mPluginEnabler;
155 
PluginPreference(Context prefContext, PackageInfo info, PluginEnabler pluginEnabler)156         public PluginPreference(Context prefContext, PackageInfo info, PluginEnabler pluginEnabler) {
157             super(prefContext);
158             PackageManager pm = prefContext.getPackageManager();
159             mHasSettings = pm.resolveActivity(new Intent(ACTION_PLUGIN_SETTINGS)
160                     .setPackage(info.packageName), 0) != null;
161             mInfo = info;
162             mPluginEnabler = pluginEnabler;
163             setTitle(info.applicationInfo.loadLabel(pm));
164             setChecked(isPluginEnabled());
165             setWidgetLayoutResource(R.layout.tuner_widget_settings_switch);
166         }
167 
isPluginEnabled()168         private boolean isPluginEnabled() {
169             for (int i = 0; i < mInfo.services.length; i++) {
170                 ComponentName componentName = new ComponentName(mInfo.packageName,
171                         mInfo.services[i].name);
172                 if (!mPluginEnabler.isEnabled(componentName)) {
173                     return false;
174                 }
175             }
176             return true;
177         }
178 
179         @Override
persistBoolean(boolean isEnabled)180         protected boolean persistBoolean(boolean isEnabled) {
181             boolean shouldSendBroadcast = false;
182             for (int i = 0; i < mInfo.services.length; i++) {
183                 ComponentName componentName = new ComponentName(mInfo.packageName,
184                         mInfo.services[i].name);
185 
186                 if (mPluginEnabler.isEnabled(componentName) != isEnabled) {
187                     if (isEnabled) {
188                         mPluginEnabler.setEnabled(componentName);
189                     } else {
190                         mPluginEnabler.setDisabled(componentName, PluginEnabler.DISABLED_MANUALLY);
191                     }
192                     shouldSendBroadcast = true;
193                 }
194             }
195             if (shouldSendBroadcast) {
196                 final String pkg = mInfo.packageName;
197                 final Intent intent = new Intent(PluginManager.PLUGIN_CHANGED,
198                         pkg != null ? Uri.fromParts("package", pkg, null) : null);
199                 getContext().sendBroadcast(intent);
200             }
201             return true;
202         }
203 
204         @Override
onBindViewHolder(PreferenceViewHolder holder)205         public void onBindViewHolder(PreferenceViewHolder holder) {
206             super.onBindViewHolder(holder);
207             holder.findViewById(R.id.settings).setVisibility(mHasSettings ? View.VISIBLE
208                     : View.GONE);
209             holder.findViewById(R.id.divider).setVisibility(mHasSettings ? View.VISIBLE
210                     : View.GONE);
211             holder.findViewById(R.id.settings).setOnClickListener(v -> {
212                 ResolveInfo result = v.getContext().getPackageManager().resolveActivity(
213                         new Intent(ACTION_PLUGIN_SETTINGS).setPackage(
214                                 mInfo.packageName), 0);
215                 if (result != null) {
216                     v.getContext().startActivity(new Intent().setComponent(
217                             new ComponentName(result.activityInfo.packageName,
218                                     result.activityInfo.name)));
219                 }
220             });
221             holder.itemView.setOnLongClickListener(v -> {
222                 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
223                 intent.setData(Uri.fromParts("package", mInfo.packageName, null));
224                 getContext().startActivity(intent);
225                 return true;
226             });
227         }
228     }
229 }
230