Add plugin support

- Add libs/plugin_core.jar
- Include plugin_core in Launcher3 build (it is already present other
  builds as part of the updated shared lib)
- Add PluginEnablerImpl that uses SharedPrefs to enable/disable plugin
  components
- Add src_plugins, where plugin interfaces will live. It has a build
  rule to create a jar that plugin projects will depend on.
- Copy PluginPreferencesFragment from sysui but using our implementation
  for PluginEnabler

Bug: 115877296
Change-Id: I3db54677eaceb10f92018c0f9d18920ad9ffac39
This commit is contained in:
Tony Wickham 2018-10-05 15:11:21 -07:00
parent 4548a7d48d
commit 76cce29d66
10 changed files with 477 additions and 0 deletions

View File

@ -28,6 +28,35 @@ LOCAL_UNINSTALLABLE_MODULE := true
LOCAL_SDK_VERSION := current
include $(BUILD_PREBUILT)
include $(CLEAR_VARS)
LOCAL_MODULE := libPluginCore
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_CLASS := JAVA_LIBRARIES
LOCAL_SRC_FILES := libs/plugin_core.jar
LOCAL_UNINSTALLABLE_MODULE := true
LOCAL_SDK_VERSION := current
include $(BUILD_PREBUILT)
#
# Build rule for plugin lib (needed to write a plugin).
#
include $(CLEAR_VARS)
LOCAL_USE_AAPT2 := true
LOCAL_AAPT2_ONLY := true
LOCAL_MODULE_TAGS := optional
LOCAL_STATIC_JAVA_LIBRARIES := libPluginCore
LOCAL_SRC_FILES := \
$(call all-java-files-under, src_plugins)
LOCAL_SDK_VERSION := current
LOCAL_MIN_SDK_VERSION := 28
LOCAL_MODULE := LauncherPluginLib
LOCAL_PRIVILEGED_MODULE := true
include $(BUILD_STATIC_JAVA_LIBRARY)
#
# Build rule for Launcher3 dependencies lib.
#
@ -40,6 +69,8 @@ LOCAL_STATIC_ANDROID_LIBRARIES := \
androidx.recyclerview_recyclerview \
androidx.dynamicanimation_dynamicanimation
LOCAL_STATIC_JAVA_LIBRARIES := libPluginCore
LOCAL_SRC_FILES := \
$(call all-proto-files-under, protos) \
$(call all-proto-files-under, proto_overrides)

BIN
libs/plugin_core.jar Normal file

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,45 @@
/*
* Copyright (C) 2018 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.launcher3.uioverrides.plugins;
import android.content.ComponentName;
import android.content.Context;
import android.content.SharedPreferences;
import com.android.launcher3.Utilities;
import com.android.systemui.shared.plugins.PluginEnabler;
public class PluginEnablerImpl implements PluginEnabler {
final private SharedPreferences mSharedPrefs;
public PluginEnablerImpl(Context context) {
mSharedPrefs = Utilities.getDevicePrefs(context);
}
@Override
public void setEnabled(ComponentName component, boolean enabled) {
mSharedPrefs.edit().putBoolean(toPrefString(component), enabled).apply();
}
@Override
public boolean isEnabled(ComponentName component) {
return mSharedPrefs.getBoolean(toPrefString(component), true);
}
private String toPrefString(ComponentName component) {
return "PLUGIN_ENABLED_" + component.flattenToString();
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright (C) 2018 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.launcher3.uioverrides.plugins;
import android.content.Context;
import android.os.Looper;
import com.android.launcher3.LauncherModel;
import com.android.systemui.shared.plugins.PluginEnabler;
import com.android.systemui.shared.plugins.PluginInitializer;
public class PluginInitializerImpl implements PluginInitializer {
@Override
public Looper getBgLooper() {
return LauncherModel.getWorkerLooper();
}
@Override
public void onPluginManagerInit() {
}
@Override
public String[] getWhitelistedPlugins(Context context) {
return new String[0];
}
@Override
public PluginEnabler getPluginEnabler(Context context) {
return new PluginEnablerImpl(context);
}
@Override
public void handleWtfs() {
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (C) 2018 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.launcher3.uioverrides.plugins;
import android.content.Context;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.shared.plugins.PluginEnabler;
import com.android.systemui.shared.plugins.PluginInitializer;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.plugins.PluginManagerImpl;
public class PluginManagerWrapper {
public static final MainThreadInitializedObject<PluginManagerWrapper> INSTANCE =
new MainThreadInitializedObject<>(PluginManagerWrapper::new);
private final PluginManager mPluginManager;
private final PluginEnabler mPluginEnabler;
private PluginManagerWrapper(Context c) {
PluginInitializer pluginInitializer = new PluginInitializerImpl();
mPluginManager = new PluginManagerImpl(c, pluginInitializer);
mPluginEnabler = pluginInitializer.getPluginEnabler(c);
}
PluginEnabler getPluginEnabler() {
return mPluginEnabler;
}
public void addPluginListener(PluginListener<? extends Plugin> listener, Class<?> pluginClass) {
mPluginManager.addPluginListener(listener, pluginClass);
}
public void removePluginListener(PluginListener<? extends Plugin> listener) {
mPluginManager.removePluginListener(listener);
}
}

View File

@ -0,0 +1,217 @@
/*
* Copyright (C) 2018 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.launcher3.uioverrides.plugins;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen;
import android.preference.SwitchPreference;
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.view.View;
import com.android.launcher3.R;
import com.android.systemui.shared.plugins.PluginEnabler;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.plugins.PluginPrefs;
import java.util.List;
import java.util.Set;
/**
* This class is copied from System UI Tuner, except using our PluginEnablerImpl. The reason we
* can't share a common base class in the shared lib is because the androidx preference dependency
* interferes with our recyclerview and fragment dependencies.
*/
public class PluginPreferencesFragment extends PreferenceFragment {
public static final String ACTION_PLUGIN_SETTINGS
= "com.android.systemui.action.PLUGIN_SETTINGS";
private static final String PLUGIN_PERMISSION = "com.android.systemui.permission.PLUGIN";
private PluginPrefs mPluginPrefs;
private PluginEnabler mPluginEnabler;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addDataScheme("package");
getContext().registerReceiver(mReceiver, filter);
filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
getContext().registerReceiver(mReceiver, filter);
mPluginEnabler = PluginManagerWrapper.INSTANCE.get(getContext()).getPluginEnabler();
loadPrefs();
}
@Override
public void onDestroy() {
super.onDestroy();
getContext().unregisterReceiver(mReceiver);
}
private void loadPrefs() {
PreferenceScreen screen = getPreferenceManager().createPreferenceScreen(getContext());
screen.setOrderingAsAdded(false);
Context prefContext = getContext();
mPluginPrefs = new PluginPrefs(getContext());
PackageManager pm = getContext().getPackageManager();
Set<String> pluginActions = mPluginPrefs.getPluginList();
ArrayMap<String, ArraySet<String>> plugins = new ArrayMap<>();
for (String action : pluginActions) {
String name = toName(action);
List<ResolveInfo> result = pm.queryIntentServices(
new Intent(action), PackageManager.MATCH_DISABLED_COMPONENTS);
for (ResolveInfo info : result) {
String packageName = info.serviceInfo.packageName;
if (!plugins.containsKey(packageName)) {
plugins.put(packageName, new ArraySet<>());
}
plugins.get(packageName).add(name);
}
}
List<PackageInfo> apps = pm.getPackagesHoldingPermissions(new String[]{PLUGIN_PERMISSION},
PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.GET_SERVICES);
apps.forEach(app -> {
if (!plugins.containsKey(app.packageName)) return;
SwitchPreference pref = new PluginPreference(prefContext, app, mPluginEnabler);
pref.setSummary("Plugins: " + toString(plugins.get(app.packageName)));
screen.addPreference(pref);
});
setPreferenceScreen(screen);
}
private String toString(ArraySet<String> plugins) {
StringBuilder b = new StringBuilder();
for (String string : plugins) {
if (b.length() != 0) {
b.append(", ");
}
b.append(string);
}
return b.toString();
}
private String toName(String action) {
String str = action.replace("com.android.systemui.action.PLUGIN_", "");
StringBuilder b = new StringBuilder();
for (String s : str.split("_")) {
if (b.length() != 0) {
b.append(' ');
}
b.append(s.substring(0, 1));
b.append(s.substring(1).toLowerCase());
}
return b.toString();
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
loadPrefs();
}
};
private static class PluginPreference extends SwitchPreference {
private final boolean mHasSettings;
private final PackageInfo mInfo;
private final PluginEnabler mPluginEnabler;
public PluginPreference(Context prefContext, PackageInfo info, PluginEnabler pluginEnabler) {
super(prefContext);
PackageManager pm = prefContext.getPackageManager();
mHasSettings = pm.resolveActivity(new Intent(ACTION_PLUGIN_SETTINGS)
.setPackage(info.packageName), 0) != null;
mInfo = info;
mPluginEnabler = pluginEnabler;
setTitle(info.applicationInfo.loadLabel(pm));
setChecked(isPluginEnabled());
setWidgetLayoutResource(R.layout.switch_preference_with_settings);
}
private boolean isPluginEnabled() {
for (int i = 0; i < mInfo.services.length; i++) {
ComponentName componentName = new ComponentName(mInfo.packageName,
mInfo.services[i].name);
if (!mPluginEnabler.isEnabled(componentName)) {
return false;
}
}
return true;
}
@Override
protected boolean persistBoolean(boolean isEnabled) {
boolean shouldSendBroadcast = false;
for (int i = 0; i < mInfo.services.length; i++) {
ComponentName componentName = new ComponentName(mInfo.packageName,
mInfo.services[i].name);
if (mPluginEnabler.isEnabled(componentName) != isEnabled) {
mPluginEnabler.setEnabled(componentName, isEnabled);
shouldSendBroadcast = true;
}
}
if (shouldSendBroadcast) {
final String pkg = mInfo.packageName;
final Intent intent = new Intent(PluginManager.PLUGIN_CHANGED,
pkg != null ? Uri.fromParts("package", pkg, null) : null);
getContext().sendBroadcast(intent);
}
setChecked(isEnabled);
return true;
}
@Override
protected void onBindView(View view) {
super.onBindView(view);
view.findViewById(R.id.settings).setVisibility(mHasSettings ? View.VISIBLE
: View.GONE);
view.findViewById(R.id.divider).setVisibility(mHasSettings ? View.VISIBLE
: View.GONE);
view.findViewById(R.id.settings).setOnClickListener(v -> {
ResolveInfo result = v.getContext().getPackageManager().resolveActivity(
new Intent(ACTION_PLUGIN_SETTINGS).setPackage(
mInfo.packageName), 0);
if (result != null) {
v.getContext().startActivity(new Intent().setComponent(
new ComponentName(result.activityInfo.packageName,
result.activityInfo.name)));
}
});
view.setOnLongClickListener(v -> {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.fromParts("package", mInfo.packageName, null));
getContext().startActivity(intent);
return true;
});
}
}
}

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2018 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_setting"
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" />
<!-- Note: seems we need focusable="false" and clickable="false" when moving to androidx -->
<Switch
android:id="@android:id/switch_widget"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@null" />
</LinearLayout>

3
src_plugins/README.md Normal file
View File

@ -0,0 +1,3 @@
This directory contains plugin interfaces that launcher listens for and plugins implement. In other words, these are the hooks that specify what plugins launcher currently supports.
Details about how to create a new plugin interface, or to use existing interfaces to write a plugin can be found at go/gnl/plugins.

View File

@ -0,0 +1,36 @@
/*
* Copyright (C) 2018 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.launcher3.uioverrides.plugins;
import android.content.Context;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginListener;
public class PluginManagerWrapper {
public static final MainThreadInitializedObject<PluginManagerWrapper> INSTANCE =
new MainThreadInitializedObject<>(PluginManagerWrapper::new);
private PluginManagerWrapper(Context c) {
}
public void addPluginListener(PluginListener<? extends Plugin> listener, Class<?> pluginClass) {
}
public void removePluginListener(PluginListener<? extends Plugin> listener) {
}
}