Add plugin controls to tuner

Allow plugins to be manually turned off from within the tuner. This
screen only shows itself if at some point in time a plugin has been
active on this device.  Plugins can also serface settings there by
receiving the com.android.systemui.action.PLUGIN_SETTINGS action.

Test: Manual
Change-Id: Ifb043c85e383fc072c6445ae322293a6401a6f2c
This commit is contained in:
Jason Monk
2016-08-23 14:41:05 -04:00
parent 46767b77c0
commit ef0d34d32e
12 changed files with 326 additions and 9 deletions

View File

@@ -21,7 +21,15 @@
<uses-permission android:name="com.android.systemui.permission.PLUGIN" />
<application>
<service android:name=".SampleOverlayPlugin">
<activity android:name=".PluginSettings"
android:label="@string/plugin_label">
<intent-filter>
<action android:name="com.android.systemui.action.PLUGIN_SETTINGS" />
</intent-filter>
</activity>
<service android:name=".SampleOverlayPlugin"
android:label="@string/plugin_label">
<intent-filter>
<action android:name="com.android.systemui.action.PLUGIN_OVERLAY" />
</intent-filter>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
** Copyright 2016, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
-->
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="@string/plugin_settings_here"
android:gravity="center" />

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/**
* Copyright (c) 2016, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="plugin_settings_here" translatable="false">Plugin settings go here</string>
<string name="plugin_label" translatable="false">Overlay Plugin</string>
</resources>

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.android.systemui.plugin.testoverlayplugin;
import android.annotation.Nullable;
import android.app.Activity;
import android.os.Bundle;
/**
* DO NOT Reference Plugin interfaces here, this runs in the plugin APK's process
* and is only for modifying settings.
*/
public class PluginSettings extends Activity {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.plugin_settings);
}
}

View File

@@ -24,7 +24,6 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
@@ -163,6 +162,7 @@ public class PluginInstanceManager<T extends Plugin> extends BroadcastReceiver {
switch (msg.what) {
case PLUGIN_CONNECTED:
if (DEBUG) Log.d(TAG, "onPluginConnected");
PluginPrefs.setHasPlugins(mContext);
PluginInfo<T> info = (PluginInfo<T>) msg.obj;
info.mPlugin.onCreate(mContext, info.mPluginContext);
mListener.onPluginConnected(info.mPlugin);

View File

@@ -37,6 +37,7 @@ public class PluginManager {
private final Context mContext;
private final PluginInstanceManagerFactory mFactory;
private final boolean isDebuggable;
private final PluginPrefs mPluginPrefs;
private PluginManager(Context context) {
this(context, new PluginInstanceManagerFactory(), Build.IS_DEBUGGABLE,
@@ -51,6 +52,7 @@ public class PluginManager {
mBackgroundThread = new HandlerThread("Plugins");
mBackgroundThread.start();
isDebuggable = debuggable;
mPluginPrefs = new PluginPrefs(mContext);
PluginExceptionHandler uncaughtExceptionHandler = new PluginExceptionHandler(
defaultHandler);
@@ -68,6 +70,7 @@ public class PluginManager {
// Never ever ever allow these on production builds, they are only for prototyping.
return;
}
mPluginPrefs.addAction(action);
PluginInstanceManager p = mFactory.createPluginInstanceManager(mContext, action, listener,
allowMultiple, mBackgroundThread.getLooper(), version);
p.startListening();

View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.android.systemui.plugins;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.ArraySet;
import java.util.Set;
/**
* Storage for all plugin actions in SharedPreferences.
*
* This allows the list of actions that the Tuner needs to search for to be generated
* instead of hard coded.
*/
public class PluginPrefs {
private static final String PREFS = "plugin_prefs";
private static final String PLUGIN_ACTIONS = "actions";
private static final String HAS_PLUGINS = "plugins";
private final Set<String> mPluginActions;
private final SharedPreferences mSharedPrefs;
public PluginPrefs(Context context) {
mSharedPrefs = context.getSharedPreferences(PREFS, 0);
mPluginActions = new ArraySet<>(mSharedPrefs.getStringSet(PLUGIN_ACTIONS, null));
}
public Set<String> getPluginList() {
return mPluginActions;
}
public synchronized void addAction(String action) {
if (mPluginActions.add(action)){
mSharedPrefs.edit().putStringSet(PLUGIN_ACTIONS, mPluginActions).commit();
}
}
public static boolean hasPlugins(Context context) {
return context.getSharedPreferences(PREFS, 0).getBoolean(HAS_PLUGINS, false);
}
public static void setHasPlugins(Context context) {
context.getSharedPreferences(PREFS, 0).edit().putBoolean(HAS_PLUGINS, true).commit();
}
}

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<ImageView
android:id="@+id/settings"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_settings"
android:tint="@android:color/black"
android:padding="12dp"
android:background="?android:attr/selectableItemBackgroundBorderless" />
<View
android:id="@+id/divider"
android:layout_width="1dp"
android:layout_height="30dp"
android:layout_marginEnd="8dp"
android:background="?android:attr/listDivider" />
<Switch
android:id="@android:id/switch_widget"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="false"
android:clickable="false"
android:background="@null" />
</LinearLayout>

View File

@@ -1667,4 +1667,8 @@
<!-- accessibility label for paging indicator in quick settings [CHAR LIMITi=NONE] -->
<string name="accessibility_quick_settings_page">Page <xliff:g name="current_page" example="1">%1$d</xliff:g> of <xliff:g name="num_pages" example="2">%2$d</xliff:g></string>
<!-- Plugin control section of the tuner. Non-translatable since it should
not appear on production builds ever. -->
<string name="plugins" translatable="false">Plugins</string>
</resources>

View File

@@ -133,6 +133,11 @@
android:title="@string/other"
android:fragment="com.android.systemui.tuner.OtherPrefs" />
<Preference
android:key="plugins"
android:title="@string/plugins"
android:fragment="com.android.systemui.tuner.PluginFragment" />
<!-- Warning, this goes last. -->
<Preference
android:summary="@string/tuner_persistent_warning"

View File

@@ -0,0 +1,111 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.android.systemui.tuner;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.support.v14.preference.PreferenceFragment;
import android.support.v14.preference.SwitchPreference;
import android.support.v7.preference.PreferenceCategory;
import android.support.v7.preference.PreferenceScreen;
import android.support.v7.preference.PreferenceViewHolder;
import android.view.View;
import com.android.systemui.plugins.PluginPrefs;
import com.android.systemui.R;
import java.util.List;
import java.util.Set;
public class PluginFragment extends PreferenceFragment {
public static final String ACTION_PLUGIN_SETTINGS
= "com.android.systemui.action.PLUGIN_SETTINGS";
private PluginPrefs mPluginPrefs;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
PreferenceScreen screen = getPreferenceManager().createPreferenceScreen(getContext());
screen.setOrderingAsAdded(false);
Context prefContext = getPreferenceManager().getContext();
mPluginPrefs = new PluginPrefs(getContext());
Set<String> pluginActions = mPluginPrefs.getPluginList();
for (String action : pluginActions) {
String name = action.replace("com.android.systemui.action.PLUGIN_", "");
PreferenceCategory category = new PreferenceCategory(prefContext);
category.setTitle(name);
List<ResolveInfo> result = getContext().getPackageManager().queryIntentServices(
new Intent(action), PackageManager.MATCH_DISABLED_COMPONENTS);
if (result.size() > 0) {
screen.addPreference(category);
}
for (ResolveInfo info : result) {
category.addPreference(new PluginPreference(prefContext, info));
}
}
setPreferenceScreen(screen);
}
private static class PluginPreference extends SwitchPreference {
private final ComponentName mComponent;
private final boolean mHasSettings;
public PluginPreference(Context prefContext, ResolveInfo info) {
super(prefContext);
mComponent = new ComponentName(info.serviceInfo.packageName, info.serviceInfo.name);
PackageManager pm = prefContext.getPackageManager();
mHasSettings = pm.resolveActivity(new Intent(ACTION_PLUGIN_SETTINGS)
.setPackage(mComponent.getPackageName()), 0) != null;
setTitle(info.serviceInfo.loadLabel(pm));
setChecked(pm.getComponentEnabledSetting(mComponent)
!= PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
setWidgetLayoutResource(R.layout.tuner_widget_settings_switch);
}
@Override
protected boolean persistBoolean(boolean value) {
getContext().getPackageManager().setComponentEnabledSetting(mComponent,
value ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
: PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
return true;
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
holder.findViewById(R.id.settings).setVisibility(mHasSettings ? View.VISIBLE
: View.GONE);
holder.findViewById(R.id.divider).setVisibility(mHasSettings ? View.VISIBLE
: View.GONE);
holder.findViewById(R.id.settings).setOnClickListener(v -> {
ResolveInfo result = v.getContext().getPackageManager().resolveActivity(
new Intent(ACTION_PLUGIN_SETTINGS).setPackage(
mComponent.getPackageName()), 0);
if (result != null) {
v.getContext().startActivity(new Intent().setComponent(
new ComponentName(result.activityInfo.packageName,
result.activityInfo.name)));
}
});
}
}
}

View File

@@ -19,16 +19,9 @@ import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.DialogInterface;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.provider.Settings;
import android.provider.Settings.System;
import android.support.v14.preference.PreferenceFragment;
import android.support.v14.preference.SwitchPreference;
import android.support.v7.preference.Preference;
import android.support.v7.preference.Preference.OnPreferenceChangeListener;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
@@ -36,12 +29,14 @@ import android.view.MenuItem;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.MetricsProto.MetricsEvent;
import com.android.systemui.R;
import com.android.systemui.plugins.PluginPrefs;
public class TunerFragment extends PreferenceFragment {
private static final String TAG = "TunerFragment";
private static final String KEY_BATTERY_PCT = "battery_pct";
private static final String KEY_PLUGINS = "plugins";
public static final String SETTING_SEEN_TUNER_WARNING = "seen_tuner_warning";
@@ -65,6 +60,9 @@ public class TunerFragment extends PreferenceFragment {
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.tuner_prefs);
if (!PluginPrefs.hasPlugins(getContext())) {
getPreferenceScreen().removePreference(findPreference(KEY_PLUGINS));
}
if (Settings.Secure.getInt(getContext().getContentResolver(), SETTING_SEEN_TUNER_WARNING,
0) == 0) {