diff options
Diffstat (limited to 'packages/SystemUI/shared/src')
40 files changed, 1577 insertions, 2471 deletions
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginEnabler.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginEnabler.java new file mode 100644 index 000000000000..74fd13f9564e --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginEnabler.java @@ -0,0 +1,25 @@ +/* + * 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.systemui.shared.plugins; + +import android.content.ComponentName; + +/** + * Enables and disables plugins. + */ +public interface PluginEnabler { + void setEnabled(ComponentName component, boolean enabled); + boolean isEnabled(ComponentName component); +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInitializer.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInitializer.java new file mode 100644 index 000000000000..c3815e4cee78 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInitializer.java @@ -0,0 +1,40 @@ +/* + * 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.systemui.shared.plugins; + +import android.content.Context; +import android.os.Looper; + +/** + * Provides necessary components for initializing {@link PluginManagerImpl}. + */ +public interface PluginInitializer { + + Looper getBgLooper(); + + /** + * Called from the bg looper during initialization of {@link PluginManagerImpl}. + */ + void onPluginManagerInit(); + + String[] getWhitelistedPlugins(Context context); + + PluginEnabler getPluginEnabler(Context context); + + /** + * Called from {@link PluginManagerImpl#handleWtfs()}. + */ + void handleWtfs(); +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java new file mode 100644 index 000000000000..8e7fadb5c7cb --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java @@ -0,0 +1,441 @@ +/* + * 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.shared.plugins; + +import android.app.Notification; +import android.app.Notification.Action; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.net.Uri; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.UserHandle; +import android.util.ArraySet; +import android.util.Log; +import android.view.LayoutInflater; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; +import com.android.systemui.plugins.Plugin; +import com.android.systemui.plugins.PluginFragment; +import com.android.systemui.plugins.PluginListener; +import com.android.systemui.shared.plugins.VersionInfo.InvalidVersionException; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class PluginInstanceManager<T extends Plugin> { + + private static final boolean DEBUG = false; + + private static final String TAG = "PluginInstanceManager"; + public static final String PLUGIN_PERMISSION = "com.android.systemui.permission.PLUGIN"; + + private final Context mContext; + private final PluginListener<T> mListener; + private final String mAction; + private final boolean mAllowMultiple; + private final VersionInfo mVersion; + + @VisibleForTesting + final MainHandler mMainHandler; + @VisibleForTesting + final PluginHandler mPluginHandler; + private final boolean isDebuggable; + private final PackageManager mPm; + private final PluginManagerImpl mManager; + private final ArraySet<String> mWhitelistedPlugins = new ArraySet<>(); + + PluginInstanceManager(Context context, String action, PluginListener<T> listener, + boolean allowMultiple, Looper looper, VersionInfo version, PluginManagerImpl manager) { + this(context, context.getPackageManager(), action, listener, allowMultiple, looper, version, + manager, Build.IS_DEBUGGABLE, manager.getWhitelistedPlugins()); + } + + @VisibleForTesting + PluginInstanceManager(Context context, PackageManager pm, String action, + PluginListener<T> listener, boolean allowMultiple, Looper looper, VersionInfo version, + PluginManagerImpl manager, boolean debuggable, String[] pluginWhitelist) { + mMainHandler = new MainHandler(Looper.getMainLooper()); + mPluginHandler = new PluginHandler(looper); + mManager = manager; + mContext = context; + mPm = pm; + mAction = action; + mListener = listener; + mAllowMultiple = allowMultiple; + mVersion = version; + mWhitelistedPlugins.addAll(Arrays.asList(pluginWhitelist)); + isDebuggable = debuggable; + } + + public PluginInfo<T> getPlugin() { + if (Looper.myLooper() != Looper.getMainLooper()) { + throw new RuntimeException("Must be called from UI thread"); + } + mPluginHandler.handleQueryPlugins(null /* All packages */); + if (mPluginHandler.mPlugins.size() > 0) { + mMainHandler.removeMessages(MainHandler.PLUGIN_CONNECTED); + PluginInfo<T> info = mPluginHandler.mPlugins.get(0); + PluginPrefs.setHasPlugins(mContext); + info.mPlugin.onCreate(mContext, info.mPluginContext); + return info; + } + return null; + } + + public void loadAll() { + if (DEBUG) Log.d(TAG, "startListening"); + mPluginHandler.sendEmptyMessage(PluginHandler.QUERY_ALL); + } + + public void destroy() { + if (DEBUG) Log.d(TAG, "stopListening"); + ArrayList<PluginInfo> plugins = new ArrayList<PluginInfo>(mPluginHandler.mPlugins); + for (PluginInfo plugin : plugins) { + mMainHandler.obtainMessage(MainHandler.PLUGIN_DISCONNECTED, + plugin.mPlugin).sendToTarget(); + } + } + + public void onPackageRemoved(String pkg) { + mPluginHandler.obtainMessage(PluginHandler.REMOVE_PKG, pkg).sendToTarget(); + } + + public void onPackageChange(String pkg) { + mPluginHandler.obtainMessage(PluginHandler.REMOVE_PKG, pkg).sendToTarget(); + mPluginHandler.obtainMessage(PluginHandler.QUERY_PKG, pkg).sendToTarget(); + } + + public boolean checkAndDisable(String className) { + boolean disableAny = false; + ArrayList<PluginInfo> plugins = new ArrayList<PluginInfo>(mPluginHandler.mPlugins); + for (PluginInfo info : plugins) { + if (className.startsWith(info.mPackage)) { + disable(info); + disableAny = true; + } + } + return disableAny; + } + + public boolean disableAll() { + ArrayList<PluginInfo> plugins = new ArrayList<PluginInfo>(mPluginHandler.mPlugins); + for (int i = 0; i < plugins.size(); i++) { + disable(plugins.get(i)); + } + return plugins.size() != 0; + } + + private void disable(PluginInfo info) { + // Live by the sword, die by the sword. + // Misbehaving plugins get disabled and won't come back until uninstall/reinstall. + + // If a plugin is detected in the stack of a crash then this will be called for that + // plugin, if the plugin causing a crash cannot be identified, they are all disabled + // assuming one of them must be bad. + if (mWhitelistedPlugins.contains(info.mPackage)) { + // Don't disable whitelisted plugins as they are a part of the OS. + return; + } + Log.w(TAG, "Disabling plugin " + info.mPackage + "/" + info.mClass); + mManager.getPluginEnabler().setEnabled(new ComponentName(info.mPackage, info.mClass), + false); + } + + public <T> boolean dependsOn(Plugin p, Class<T> cls) { + ArrayList<PluginInfo> plugins = new ArrayList<PluginInfo>(mPluginHandler.mPlugins); + for (PluginInfo info : plugins) { + if (info.mPlugin.getClass().getName().equals(p.getClass().getName())) { + return info.mVersion != null && info.mVersion.hasClass(cls); + } + } + return false; + } + + @Override + public String toString() { + return String.format("%s@%s (action=%s)", + getClass().getSimpleName(), hashCode(), mAction); + } + + private class MainHandler extends Handler { + private static final int PLUGIN_CONNECTED = 1; + private static final int PLUGIN_DISCONNECTED = 2; + + public MainHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case PLUGIN_CONNECTED: + if (DEBUG) Log.d(TAG, "onPluginConnected"); + PluginPrefs.setHasPlugins(mContext); + PluginInfo<T> info = (PluginInfo<T>) msg.obj; + mManager.handleWtfs(); + if (!(msg.obj instanceof PluginFragment)) { + // Only call onDestroy for plugins that aren't fragments, as fragments + // will get the onCreate as part of the fragment lifecycle. + info.mPlugin.onCreate(mContext, info.mPluginContext); + } + mListener.onPluginConnected(info.mPlugin, info.mPluginContext); + break; + case PLUGIN_DISCONNECTED: + if (DEBUG) Log.d(TAG, "onPluginDisconnected"); + mListener.onPluginDisconnected((T) msg.obj); + if (!(msg.obj instanceof PluginFragment)) { + // Only call onDestroy for plugins that aren't fragments, as fragments + // will get the onDestroy as part of the fragment lifecycle. + ((T) msg.obj).onDestroy(); + } + break; + default: + super.handleMessage(msg); + break; + } + } + } + + private class PluginHandler extends Handler { + private static final int QUERY_ALL = 1; + private static final int QUERY_PKG = 2; + private static final int REMOVE_PKG = 3; + + private final ArrayList<PluginInfo<T>> mPlugins = new ArrayList<>(); + + public PluginHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case QUERY_ALL: + if (DEBUG) Log.d(TAG, "queryAll " + mAction); + for (int i = mPlugins.size() - 1; i >= 0; i--) { + PluginInfo<T> plugin = mPlugins.get(i); + mListener.onPluginDisconnected(plugin.mPlugin); + if (!(plugin.mPlugin instanceof PluginFragment)) { + // Only call onDestroy for plugins that aren't fragments, as fragments + // will get the onDestroy as part of the fragment lifecycle. + plugin.mPlugin.onDestroy(); + } + } + mPlugins.clear(); + handleQueryPlugins(null); + break; + case REMOVE_PKG: + String pkg = (String) msg.obj; + for (int i = mPlugins.size() - 1; i >= 0; i--) { + final PluginInfo<T> plugin = mPlugins.get(i); + if (plugin.mPackage.equals(pkg)) { + mMainHandler.obtainMessage(MainHandler.PLUGIN_DISCONNECTED, + plugin.mPlugin).sendToTarget(); + mPlugins.remove(i); + } + } + break; + case QUERY_PKG: + String p = (String) msg.obj; + if (DEBUG) Log.d(TAG, "queryPkg " + p + " " + mAction); + if (mAllowMultiple || (mPlugins.size() == 0)) { + handleQueryPlugins(p); + } else { + if (DEBUG) Log.d(TAG, "Too many of " + mAction); + } + break; + default: + super.handleMessage(msg); + } + } + + private void handleQueryPlugins(String pkgName) { + // This isn't actually a service and shouldn't ever be started, but is + // a convenient PM based way to manage our plugins. + Intent intent = new Intent(mAction); + if (pkgName != null) { + intent.setPackage(pkgName); + } + List<ResolveInfo> result = mPm.queryIntentServices(intent, 0); + if (DEBUG) Log.d(TAG, "Found " + result.size() + " plugins"); + if (result.size() > 1 && !mAllowMultiple) { + // TODO: Show warning. + Log.w(TAG, "Multiple plugins found for " + mAction); + return; + } + for (ResolveInfo info : result) { + ComponentName name = new ComponentName(info.serviceInfo.packageName, + info.serviceInfo.name); + PluginInfo<T> t = handleLoadPlugin(name); + if (t == null) continue; + mMainHandler.obtainMessage(mMainHandler.PLUGIN_CONNECTED, t).sendToTarget(); + mPlugins.add(t); + } + } + + protected PluginInfo<T> handleLoadPlugin(ComponentName component) { + // This was already checked, but do it again here to make extra extra sure, we don't + // use these on production builds. + if (!isDebuggable && !mWhitelistedPlugins.contains(component.getPackageName())) { + // Never ever ever allow these on production builds, they are only for prototyping. + Log.w(TAG, "Plugin cannot be loaded on production build: " + component); + return null; + } + if (!mManager.getPluginEnabler().isEnabled(component)) { + if (DEBUG) Log.d(TAG, "Plugin is not enabled, aborting load: " + component); + return null; + } + String pkg = component.getPackageName(); + String cls = component.getClassName(); + try { + ApplicationInfo info = mPm.getApplicationInfo(pkg, 0); + // TODO: This probably isn't needed given that we don't have IGNORE_SECURITY on + if (mPm.checkPermission(PLUGIN_PERMISSION, pkg) + != PackageManager.PERMISSION_GRANTED) { + Log.d(TAG, "Plugin doesn't have permission: " + pkg); + return null; + } + // Create our own ClassLoader so we can use our own code as the parent. + ClassLoader classLoader = mManager.getClassLoader(info.sourceDir, info.packageName); + Context pluginContext = new PluginContextWrapper( + mContext.createApplicationContext(info, 0), classLoader); + Class<?> pluginClass = Class.forName(cls, true, classLoader); + // TODO: Only create the plugin before version check if we need it for + // legacy version check. + T plugin = (T) pluginClass.newInstance(); + try { + VersionInfo version = checkVersion(pluginClass, plugin, mVersion); + if (DEBUG) Log.d(TAG, "createPlugin"); + return new PluginInfo(pkg, cls, plugin, pluginContext, version); + } catch (InvalidVersionException e) { + final int icon = mContext.getResources().getIdentifier("tuner", "drawable", + mContext.getPackageName()); + final int color = Resources.getSystem().getIdentifier( + "system_notification_accent_color", "color", "android"); + final Notification.Builder nb = new Notification.Builder(mContext, + PluginManager.NOTIFICATION_CHANNEL_ID) + .setStyle(new Notification.BigTextStyle()) + .setSmallIcon(icon) + .setWhen(0) + .setShowWhen(false) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setColor(mContext.getColor(color)); + String label = cls; + try { + label = mPm.getServiceInfo(component, 0).loadLabel(mPm).toString(); + } catch (NameNotFoundException e2) { + } + if (!e.isTooNew()) { + // Localization not required as this will never ever appear in a user build. + nb.setContentTitle("Plugin \"" + label + "\" is too old") + .setContentText("Contact plugin developer to get an updated" + + " version.\n" + e.getMessage()); + } else { + // Localization not required as this will never ever appear in a user build. + nb.setContentTitle("Plugin \"" + label + "\" is too new") + .setContentText("Check to see if an OTA is available.\n" + + e.getMessage()); + } + Intent i = new Intent(PluginManagerImpl.DISABLE_PLUGIN).setData( + Uri.parse("package://" + component.flattenToString())); + PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, i, 0); + nb.addAction(new Action.Builder(null, "Disable plugin", pi).build()); + mContext.getSystemService(NotificationManager.class) + .notifyAsUser(cls, SystemMessage.NOTE_PLUGIN, nb.build(), + UserHandle.ALL); + // TODO: Warn user. + Log.w(TAG, "Plugin has invalid interface version " + plugin.getVersion() + + ", expected " + mVersion); + return null; + } + } catch (Throwable e) { + Log.w(TAG, "Couldn't load plugin: " + pkg, e); + return null; + } + } + + private VersionInfo checkVersion(Class<?> pluginClass, T plugin, VersionInfo version) + throws InvalidVersionException { + VersionInfo pv = new VersionInfo().addClass(pluginClass); + if (pv.hasVersionInfo()) { + version.checkVersion(pv); + } else { + int fallbackVersion = plugin.getVersion(); + if (fallbackVersion != version.getDefaultVersion()) { + throw new InvalidVersionException("Invalid legacy version", false); + } + return null; + } + return pv; + } + } + + public static class PluginContextWrapper extends ContextWrapper { + private final ClassLoader mClassLoader; + private LayoutInflater mInflater; + + public PluginContextWrapper(Context base, ClassLoader classLoader) { + super(base); + mClassLoader = classLoader; + } + + @Override + public ClassLoader getClassLoader() { + return mClassLoader; + } + + @Override + public Object getSystemService(String name) { + if (LAYOUT_INFLATER_SERVICE.equals(name)) { + if (mInflater == null) { + mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this); + } + return mInflater; + } + return getBaseContext().getSystemService(name); + } + } + + static class PluginInfo<T> { + private final Context mPluginContext; + private final VersionInfo mVersion; + private String mClass; + T mPlugin; + String mPackage; + + public PluginInfo(String pkg, String cls, T plugin, Context pluginContext, + VersionInfo info) { + mPlugin = plugin; + mClass = cls; + mPackage = pkg; + mPluginContext = pluginContext; + mVersion = info; + } + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManager.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManager.java new file mode 100644 index 000000000000..3f907a8aa348 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManager.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2017 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.shared.plugins; + +import android.text.TextUtils; + +import com.android.systemui.plugins.Plugin; +import com.android.systemui.plugins.PluginListener; +import com.android.systemui.plugins.annotations.ProvidesInterface; + +public interface PluginManager { + + String PLUGIN_CHANGED = "com.android.systemui.action.PLUGIN_CHANGED"; + + // must be one of the channels created in NotificationChannels.java + String NOTIFICATION_CHANNEL_ID = "ALR"; + + String[] getWhitelistedPlugins(); + + <T extends Plugin> T getOneShotPlugin(Class<T> cls); + <T extends Plugin> T getOneShotPlugin(String action, Class<?> cls); + + <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls); + <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls, + boolean allowMultiple); + <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener, + Class<?> cls); + <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener, + Class cls, boolean allowMultiple); + + void removePluginListener(PluginListener<?> listener); + + <T> boolean dependsOn(Plugin p, Class<T> cls); + + class Helper { + public static <P> String getAction(Class<P> cls) { + ProvidesInterface info = cls.getDeclaredAnnotation(ProvidesInterface.class); + if (info == null) { + throw new RuntimeException(cls + " doesn't provide an interface"); + } + if (TextUtils.isEmpty(info.action())) { + throw new RuntimeException(cls + " doesn't provide an action"); + } + return info.action(); + } + } + +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java new file mode 100644 index 000000000000..dc2a9bd5105b --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java @@ -0,0 +1,401 @@ +/* + * 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.shared.plugins; + +import android.app.Notification; +import android.app.Notification.Action; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Resources; +import android.net.Uri; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Log; +import android.widget.Toast; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; +import com.android.systemui.plugins.Plugin; +import com.android.systemui.plugins.PluginListener; +import com.android.systemui.plugins.annotations.ProvidesInterface; +import com.android.systemui.shared.plugins.PluginInstanceManager.PluginContextWrapper; +import com.android.systemui.shared.plugins.PluginInstanceManager.PluginInfo; + +import dalvik.system.PathClassLoader; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.lang.Thread.UncaughtExceptionHandler; +import java.util.Arrays; +import java.util.Map; +/** + * @see Plugin + */ +public class PluginManagerImpl extends BroadcastReceiver implements PluginManager { + + private static final String TAG = PluginManagerImpl.class.getSimpleName(); + static final String DISABLE_PLUGIN = "com.android.systemui.action.DISABLE_PLUGIN"; + + private static PluginManager sInstance; + + private final ArrayMap<PluginListener<?>, PluginInstanceManager> mPluginMap + = new ArrayMap<>(); + private final Map<String, ClassLoader> mClassLoaders = new ArrayMap<>(); + private final ArraySet<String> mOneShotPackages = new ArraySet<>(); + private final ArraySet<String> mWhitelistedPlugins = new ArraySet<>(); + private final Context mContext; + private final PluginInstanceManagerFactory mFactory; + private final boolean isDebuggable; + private final PluginPrefs mPluginPrefs; + private final PluginEnabler mPluginEnabler; + private final PluginInitializer mPluginInitializer; + private ClassLoaderFilter mParentClassLoader; + private boolean mListening; + private boolean mHasOneShot; + private Looper mLooper; + + public PluginManagerImpl(Context context, PluginInitializer initializer) { + this(context, new PluginInstanceManagerFactory(), Build.IS_DEBUGGABLE, + Thread.getUncaughtExceptionPreHandler(), initializer); + } + + @VisibleForTesting + PluginManagerImpl(Context context, PluginInstanceManagerFactory factory, boolean debuggable, + UncaughtExceptionHandler defaultHandler, final PluginInitializer initializer) { + mContext = context; + mFactory = factory; + mLooper = initializer.getBgLooper(); + isDebuggable = debuggable; + mWhitelistedPlugins.addAll(Arrays.asList(initializer.getWhitelistedPlugins(mContext))); + mPluginPrefs = new PluginPrefs(mContext); + mPluginEnabler = initializer.getPluginEnabler(mContext); + mPluginInitializer = initializer; + + PluginExceptionHandler uncaughtExceptionHandler = new PluginExceptionHandler( + defaultHandler); + Thread.setUncaughtExceptionPreHandler(uncaughtExceptionHandler); + + new Handler(mLooper).post(new Runnable() { + @Override + public void run() { + initializer.onPluginManagerInit(); + } + }); + } + + public String[] getWhitelistedPlugins() { + return mWhitelistedPlugins.toArray(new String[0]); + } + + public PluginEnabler getPluginEnabler() { + return mPluginEnabler; + } + + public <T extends Plugin> T getOneShotPlugin(Class<T> cls) { + ProvidesInterface info = cls.getDeclaredAnnotation(ProvidesInterface.class); + if (info == null) { + throw new RuntimeException(cls + " doesn't provide an interface"); + } + if (TextUtils.isEmpty(info.action())) { + throw new RuntimeException(cls + " doesn't provide an action"); + } + return getOneShotPlugin(info.action(), cls); + } + + public <T extends Plugin> T getOneShotPlugin(String action, Class<?> cls) { + if (Looper.myLooper() != Looper.getMainLooper()) { + throw new RuntimeException("Must be called from UI thread"); + } + // Passing null causes compiler to complain about incompatible (generic) types. + PluginListener<Plugin> dummy = null; + PluginInstanceManager<T> p = mFactory.createPluginInstanceManager(mContext, action, dummy, + false, mLooper, cls, this); + mPluginPrefs.addAction(action); + PluginInfo<T> info = p.getPlugin(); + if (info != null) { + mOneShotPackages.add(info.mPackage); + mHasOneShot = true; + startListening(); + return info.mPlugin; + } + return null; + } + + public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls) { + addPluginListener(listener, cls, false); + } + + public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls, + boolean allowMultiple) { + addPluginListener(PluginManager.Helper.getAction(cls), listener, cls, allowMultiple); + } + + public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener, + Class<?> cls) { + addPluginListener(action, listener, cls, false); + } + + public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener, + Class cls, boolean allowMultiple) { + mPluginPrefs.addAction(action); + PluginInstanceManager p = mFactory.createPluginInstanceManager(mContext, action, listener, + allowMultiple, mLooper, cls, this); + p.loadAll(); + mPluginMap.put(listener, p); + startListening(); + } + + public void removePluginListener(PluginListener<?> listener) { + if (!mPluginMap.containsKey(listener)) return; + mPluginMap.remove(listener).destroy(); + if (mPluginMap.size() == 0) { + stopListening(); + } + } + + private void startListening() { + if (mListening) return; + mListening = true; + IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); + filter.addAction(Intent.ACTION_PACKAGE_CHANGED); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addAction(PLUGIN_CHANGED); + filter.addAction(DISABLE_PLUGIN); + filter.addDataScheme("package"); + mContext.registerReceiver(this, filter); + filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED); + mContext.registerReceiver(this, filter); + } + + private void stopListening() { + // Never stop listening if a one-shot is present. + if (!mListening || mHasOneShot) return; + mListening = false; + mContext.unregisterReceiver(this); + } + + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) { + for (PluginInstanceManager manager : mPluginMap.values()) { + manager.loadAll(); + } + } else if (DISABLE_PLUGIN.equals(intent.getAction())) { + Uri uri = intent.getData(); + ComponentName component = ComponentName.unflattenFromString( + uri.toString().substring(10)); + if (mWhitelistedPlugins.contains(component.getPackageName())) { + // Don't disable whitelisted plugins as they are a part of the OS. + return; + } + getPluginEnabler().setEnabled(component, false); + mContext.getSystemService(NotificationManager.class).cancel(component.getClassName(), + SystemMessage.NOTE_PLUGIN); + } else { + Uri data = intent.getData(); + String pkg = data.getEncodedSchemeSpecificPart(); + if (mOneShotPackages.contains(pkg)) { + int icon = mContext.getResources().getIdentifier("tuner", "drawable", + mContext.getPackageName()); + int color = Resources.getSystem().getIdentifier( + "system_notification_accent_color", "color", "android"); + String label = pkg; + try { + PackageManager pm = mContext.getPackageManager(); + label = pm.getApplicationInfo(pkg, 0).loadLabel(pm).toString(); + } catch (NameNotFoundException e) { + } + // Localization not required as this will never ever appear in a user build. + final Notification.Builder nb = + new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID) + .setSmallIcon(icon) + .setWhen(0) + .setShowWhen(false) + .setPriority(Notification.PRIORITY_MAX) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setColor(mContext.getColor(color)) + .setContentTitle("Plugin \"" + label + "\" has updated") + .setContentText("Restart SysUI for changes to take effect."); + Intent i = new Intent("com.android.systemui.action.RESTART").setData( + Uri.parse("package://" + pkg)); + PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, i, 0); + nb.addAction(new Action.Builder(null, "Restart SysUI", pi).build()); + mContext.getSystemService(NotificationManager.class).notifyAsUser(pkg, + SystemMessage.NOTE_PLUGIN, nb.build(), UserHandle.ALL); + } + if (clearClassLoader(pkg)) { + if (Build.IS_ENG) { + Toast.makeText(mContext, "Reloading " + pkg, Toast.LENGTH_LONG).show(); + } else { + Log.v(TAG, "Reloading " + pkg); + } + } + if (!Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) { + for (PluginInstanceManager manager : mPluginMap.values()) { + manager.onPackageChange(pkg); + } + } else { + for (PluginInstanceManager manager : mPluginMap.values()) { + manager.onPackageRemoved(pkg); + } + } + } + } + + public ClassLoader getClassLoader(String sourceDir, String pkg) { + if (!isDebuggable && !mWhitelistedPlugins.contains(pkg)) { + Log.w(TAG, "Cannot get class loader for non-whitelisted plugin. Src:" + sourceDir + + ", pkg: " + pkg); + return null; + } + if (mClassLoaders.containsKey(pkg)) { + return mClassLoaders.get(pkg); + } + ClassLoader classLoader = new PathClassLoader(sourceDir, getParentClassLoader()); + mClassLoaders.put(pkg, classLoader); + return classLoader; + } + + private boolean clearClassLoader(String pkg) { + return mClassLoaders.remove(pkg) != null; + } + + ClassLoader getParentClassLoader() { + if (mParentClassLoader == null) { + // Lazily load this so it doesn't have any effect on devices without plugins. + mParentClassLoader = new ClassLoaderFilter(getClass().getClassLoader(), + "com.android.systemui.plugin"); + } + return mParentClassLoader; + } + + public Context getContext(ApplicationInfo info, String pkg) throws NameNotFoundException { + ClassLoader classLoader = getClassLoader(info.sourceDir, pkg); + return new PluginContextWrapper(mContext.createApplicationContext(info, 0), classLoader); + } + + public <T> boolean dependsOn(Plugin p, Class<T> cls) { + for (int i = 0; i < mPluginMap.size(); i++) { + if (mPluginMap.valueAt(i).dependsOn(p, cls)) { + return true; + } + } + return false; + } + + public void handleWtfs() { + mPluginInitializer.handleWtfs(); + } + + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println(String.format(" plugin map (%d):", mPluginMap.size())); + for (PluginListener listener: mPluginMap.keySet()) { + pw.println(String.format(" %s -> %s", + listener, mPluginMap.get(listener))); + } + } + + @VisibleForTesting + public static class PluginInstanceManagerFactory { + public <T extends Plugin> PluginInstanceManager createPluginInstanceManager(Context context, + String action, PluginListener<T> listener, boolean allowMultiple, Looper looper, + Class<?> cls, PluginManagerImpl manager) { + return new PluginInstanceManager(context, action, listener, allowMultiple, looper, + new VersionInfo().addClass(cls), manager); + } + } + + // This allows plugins to include any libraries or copied code they want by only including + // classes from the plugin library. + private static class ClassLoaderFilter extends ClassLoader { + private final String mPackage; + private final ClassLoader mBase; + + public ClassLoaderFilter(ClassLoader base, String pkg) { + super(ClassLoader.getSystemClassLoader()); + mBase = base; + mPackage = pkg; + } + + @Override + protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { + if (!name.startsWith(mPackage)) super.loadClass(name, resolve); + return mBase.loadClass(name); + } + } + + private class PluginExceptionHandler implements UncaughtExceptionHandler { + private final UncaughtExceptionHandler mHandler; + + private PluginExceptionHandler(UncaughtExceptionHandler handler) { + mHandler = handler; + } + + @Override + public void uncaughtException(Thread thread, Throwable throwable) { + if (SystemProperties.getBoolean("plugin.debugging", false)) { + mHandler.uncaughtException(thread, throwable); + return; + } + // Search for and disable plugins that may have been involved in this crash. + boolean disabledAny = checkStack(throwable); + if (!disabledAny) { + // We couldn't find any plugins involved in this crash, just to be safe + // disable all the plugins, so we can be sure that SysUI is running as + // best as possible. + for (PluginInstanceManager manager : mPluginMap.values()) { + disabledAny |= manager.disableAll(); + } + } + if (disabledAny) { + throwable = new CrashWhilePluginActiveException(throwable); + } + + // Run the normal exception handler so we can crash and cleanup our state. + mHandler.uncaughtException(thread, throwable); + } + + private boolean checkStack(Throwable throwable) { + if (throwable == null) return false; + boolean disabledAny = false; + for (StackTraceElement element : throwable.getStackTrace()) { + for (PluginInstanceManager manager : mPluginMap.values()) { + disabledAny |= manager.checkAndDisable(element.getClassName()); + } + } + return disabledAny | checkStack(throwable.getCause()); + } + } + + public static class CrashWhilePluginActiveException extends RuntimeException { + public CrashWhilePluginActiveException(Throwable throwable) { + super(throwable); + } + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginPrefs.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginPrefs.java new file mode 100644 index 000000000000..c0c5d7051cea --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginPrefs.java @@ -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.shared.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(); + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/VersionInfo.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/VersionInfo.java new file mode 100644 index 000000000000..bb845cd87923 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/VersionInfo.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2017 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.shared.plugins; + +import android.util.ArrayMap; + +import com.android.systemui.plugins.annotations.Dependencies; +import com.android.systemui.plugins.annotations.DependsOn; +import com.android.systemui.plugins.annotations.ProvidesInterface; +import com.android.systemui.plugins.annotations.Requirements; +import com.android.systemui.plugins.annotations.Requires; + +import java.util.function.BiConsumer; + +public class VersionInfo { + + private final ArrayMap<Class<?>, Version> mVersions = new ArrayMap<>(); + private Class<?> mDefault; + + public boolean hasVersionInfo() { + return !mVersions.isEmpty(); + } + + public int getDefaultVersion() { + return mVersions.get(mDefault).mVersion; + } + + public VersionInfo addClass(Class<?> cls) { + if (mDefault == null) { + // The legacy default version is from the first class we add. + mDefault = cls; + } + addClass(cls, false); + return this; + } + + private void addClass(Class<?> cls, boolean required) { + if (mVersions.containsKey(cls)) return; + ProvidesInterface provider = cls.getDeclaredAnnotation(ProvidesInterface.class); + if (provider != null) { + mVersions.put(cls, new Version(provider.version(), true)); + } + Requires requires = cls.getDeclaredAnnotation(Requires.class); + if (requires != null) { + mVersions.put(requires.target(), new Version(requires.version(), required)); + } + Requirements requirements = cls.getDeclaredAnnotation(Requirements.class); + if (requirements != null) { + for (Requires r : requirements.value()) { + mVersions.put(r.target(), new Version(r.version(), required)); + } + } + DependsOn depends = cls.getDeclaredAnnotation(DependsOn.class); + if (depends != null) { + addClass(depends.target(), true); + } + Dependencies dependencies = cls.getDeclaredAnnotation(Dependencies.class); + if (dependencies != null) { + for (DependsOn d : dependencies.value()) { + addClass(d.target(), true); + } + } + } + + public void checkVersion(VersionInfo plugin) throws InvalidVersionException { + final ArrayMap<Class<?>, Version> versions = new ArrayMap<>(mVersions); + plugin.mVersions.forEach(new BiConsumer<Class<?>, Version>() { + @Override + public void accept(Class<?> aClass, Version version) { + Version v = versions.remove(aClass); + if (v == null) { + v = VersionInfo.this.createVersion(aClass); + } + if (v == null) { + throw new InvalidVersionException(aClass.getSimpleName() + + " does not provide an interface", false); + } + if (v.mVersion != version.mVersion) { + throw new InvalidVersionException(aClass, v.mVersion < version.mVersion, + v.mVersion, + version.mVersion); + } + } + }); + versions.forEach(new BiConsumer<Class<?>, Version>() { + @Override + public void accept(Class<?> aClass, Version version) { + if (version.mRequired) { + throw new InvalidVersionException("Missing required dependency " + + aClass.getSimpleName(), false); + } + } + }); + } + + private Version createVersion(Class<?> cls) { + ProvidesInterface provider = cls.getDeclaredAnnotation(ProvidesInterface.class); + if (provider != null) { + return new Version(provider.version(), false); + } + return null; + } + + public <T> boolean hasClass(Class<T> cls) { + return mVersions.containsKey(cls); + } + + public static class InvalidVersionException extends RuntimeException { + private final boolean mTooNew; + + public InvalidVersionException(String str, boolean tooNew) { + super(str); + mTooNew = tooNew; + } + + public InvalidVersionException(Class<?> cls, boolean tooNew, int expected, int actual) { + super(cls.getSimpleName() + " expected version " + expected + " but had " + actual); + mTooNew = tooNew; + } + + public boolean isTooNew() { + return mTooNew; + } + } + + private static class Version { + + private final int mVersion; + private final boolean mRequired; + + public Version(int version, boolean required) { + mVersion = version; + mRequired = required; + } + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl index ebfadd881c19..ece2bb9a9507 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl @@ -17,7 +17,7 @@ package com.android.systemui.shared.recents; import android.graphics.Rect; -import com.android.systemui.shared.system.GraphicBufferCompat; +import android.view.MotionEvent; /** * Temporary callbacks into SystemUI. @@ -26,9 +26,10 @@ interface ISystemUiProxy { /** * Proxies SurfaceControl.screenshotToBuffer(). + * @Removed + * GraphicBufferCompat screenshot(in Rect sourceCrop, int width, int height, int minLayer, + * int maxLayer, boolean useIdentityTransform, int rotation) = 0; */ - GraphicBufferCompat screenshot(in Rect sourceCrop, int width, int height, int minLayer, - int maxLayer, boolean useIdentityTransform, int rotation) = 0; /** * Begins screen pinning on the provided {@param taskId}. @@ -60,4 +61,14 @@ interface ISystemUiProxy { * needed from current value */ void setBackButtonAlpha(float alpha, boolean animate) = 8; + + /** + * Proxies motion events from the homescreen UI to the status bar. Only called when + * swipe down is detected on WORKSPACE. The sender guarantees the following order of events on + * the tracking pointer. + * + * Normal gesture: DOWN, MOVE/POINTER_DOWN/POINTER_UP)*, UP or CANCLE + */ + void onStatusBarMotionEvent(in MotionEvent event) = 9; + } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/BackgroundTaskLoader.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/BackgroundTaskLoader.java deleted file mode 100644 index a0e7752e13fa..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/BackgroundTaskLoader.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (C) 2017 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.shared.recents.model; - -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.os.Handler; -import android.os.HandlerThread; -import android.util.Log; - -import com.android.systemui.shared.system.ActivityManagerWrapper; - -/** - * Background task resource loader - */ -class BackgroundTaskLoader implements Runnable { - static String TAG = "BackgroundTaskLoader"; - static boolean DEBUG = false; - - private Context mContext; - private final HandlerThread mLoadThread; - private final Handler mLoadThreadHandler; - private final Handler mMainThreadHandler; - - private final TaskResourceLoadQueue mLoadQueue; - private final IconLoader mIconLoader; - - private boolean mStarted; - private boolean mCancelled; - private boolean mWaitingOnLoadQueue; - - private final OnIdleChangedListener mOnIdleChangedListener; - - /** Constructor, creates a new loading thread that loads task resources in the background */ - public BackgroundTaskLoader(TaskResourceLoadQueue loadQueue, - IconLoader iconLoader, OnIdleChangedListener onIdleChangedListener) { - mLoadQueue = loadQueue; - mIconLoader = iconLoader; - mMainThreadHandler = new Handler(); - mOnIdleChangedListener = onIdleChangedListener; - mLoadThread = new HandlerThread("Recents-TaskResourceLoader", - android.os.Process.THREAD_PRIORITY_BACKGROUND); - mLoadThread.start(); - mLoadThreadHandler = new Handler(mLoadThread.getLooper()); - } - - /** Restarts the loader thread */ - void start(Context context) { - mContext = context; - mCancelled = false; - if (!mStarted) { - // Start loading on the load thread - mStarted = true; - mLoadThreadHandler.post(this); - } else { - // Notify the load thread to start loading again - synchronized (mLoadThread) { - mLoadThread.notifyAll(); - } - } - } - - /** Requests the loader thread to stop after the current iteration */ - void stop() { - // Mark as cancelled for the thread to pick up - mCancelled = true; - // If we are waiting for the load queue for more tasks, then we can just reset the - // Context now, since nothing is using it - if (mWaitingOnLoadQueue) { - mContext = null; - } - } - - @Override - public void run() { - while (true) { - if (mCancelled) { - // We have to unset the context here, since the background thread may be using it - // when we call stop() - mContext = null; - // If we are cancelled, then wait until we are started again - synchronized(mLoadThread) { - try { - mLoadThread.wait(); - } catch (InterruptedException ie) { - ie.printStackTrace(); - } - } - } else { - // If we've stopped the loader, then fall through to the above logic to wait on - // the load thread - processLoadQueueItem(); - - // If there are no other items in the list, then just wait until something is added - if (!mCancelled && mLoadQueue.isEmpty()) { - synchronized(mLoadQueue) { - try { - mWaitingOnLoadQueue = true; - mMainThreadHandler.post( - () -> mOnIdleChangedListener.onIdleChanged(true)); - mLoadQueue.wait(); - mMainThreadHandler.post( - () -> mOnIdleChangedListener.onIdleChanged(false)); - mWaitingOnLoadQueue = false; - } catch (InterruptedException ie) { - ie.printStackTrace(); - } - } - } - } - } - } - - /** - * This needs to be in a separate method to work around an surprising interpreter behavior: - * The register will keep the local reference to cachedThumbnailData even if it falls out of - * scope. Putting it into a method fixes this issue. - */ - private void processLoadQueueItem() { - // Load the next item from the queue - final Task t = mLoadQueue.nextTask(); - if (t != null) { - final Drawable icon = mIconLoader.getIcon(t); - if (DEBUG) Log.d(TAG, "Loading thumbnail: " + t.key); - final ThumbnailData thumbnailData = - ActivityManagerWrapper.getInstance().getTaskThumbnail(t.key.id, - true /* reducedResolution */); - - if (!mCancelled) { - // Notify that the task data has changed - mMainThreadHandler.post( - () -> t.notifyTaskDataLoaded(thumbnailData, icon)); - } - } - } - - interface OnIdleChangedListener { - void onIdleChanged(boolean idle); - } -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/FilteredTaskList.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/FilteredTaskList.java deleted file mode 100644 index 898d64a1ea1a..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/FilteredTaskList.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) 2017 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.shared.recents.model; - -import android.util.ArrayMap; -import android.util.SparseArray; - -import com.android.systemui.shared.recents.model.Task.TaskKey; - -import java.util.ArrayList; -import java.util.List; - -/** - * A list of filtered tasks. - */ -class FilteredTaskList { - - private final ArrayList<Task> mTasks = new ArrayList<>(); - private final ArrayList<Task> mFilteredTasks = new ArrayList<>(); - private final ArrayMap<TaskKey, Integer> mFilteredTaskIndices = new ArrayMap<>(); - private TaskFilter mFilter; - - /** Sets the task filter, and returns whether the set of filtered tasks have changed. */ - boolean setFilter(TaskFilter filter) { - ArrayList<Task> prevFilteredTasks = new ArrayList<>(mFilteredTasks); - mFilter = filter; - updateFilteredTasks(); - return !prevFilteredTasks.equals(mFilteredTasks); - } - - /** Adds a new task to the task list */ - void add(Task t) { - mTasks.add(t); - updateFilteredTasks(); - } - - /** Sets the list of tasks */ - void set(List<Task> tasks) { - mTasks.clear(); - mTasks.addAll(tasks); - updateFilteredTasks(); - } - - /** Removes a task from the base list only if it is in the filtered list */ - boolean remove(Task t) { - if (mFilteredTasks.contains(t)) { - boolean removed = mTasks.remove(t); - updateFilteredTasks(); - return removed; - } - return false; - } - - /** Returns the index of this task in the list of filtered tasks */ - int indexOf(Task t) { - if (t != null && mFilteredTaskIndices.containsKey(t.key)) { - return mFilteredTaskIndices.get(t.key); - } - return -1; - } - - /** Returns the size of the list of filtered tasks */ - int size() { - return mFilteredTasks.size(); - } - - /** Returns whether the filtered list contains this task */ - boolean contains(Task t) { - return mFilteredTaskIndices.containsKey(t.key); - } - - /** Updates the list of filtered tasks whenever the base task list changes */ - private void updateFilteredTasks() { - mFilteredTasks.clear(); - if (mFilter != null) { - // Create a sparse array from task id to Task - SparseArray<Task> taskIdMap = new SparseArray<>(); - int taskCount = mTasks.size(); - for (int i = 0; i < taskCount; i++) { - Task t = mTasks.get(i); - taskIdMap.put(t.key.id, t); - } - - for (int i = 0; i < taskCount; i++) { - Task t = mTasks.get(i); - if (mFilter.acceptTask(taskIdMap, t, i)) { - mFilteredTasks.add(t); - } - } - } else { - mFilteredTasks.addAll(mTasks); - } - updateFilteredTaskIndices(); - } - - /** Updates the mapping of tasks to indices. */ - private void updateFilteredTaskIndices() { - int taskCount = mFilteredTasks.size(); - mFilteredTaskIndices.clear(); - for (int i = 0; i < taskCount; i++) { - Task t = mFilteredTasks.get(i); - mFilteredTaskIndices.put(t.key, i); - } - } - - /** Returns the list of filtered tasks */ - ArrayList<Task> getTasks() { - return mFilteredTasks; - } -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/HighResThumbnailLoader.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/HighResThumbnailLoader.java deleted file mode 100644 index 24ba99840165..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/HighResThumbnailLoader.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright (C) 2017 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.shared.recents.model; - -import static android.os.Process.setThreadPriority; - -import android.os.Handler; -import android.os.Looper; -import android.os.SystemClock; -import android.util.ArraySet; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.shared.recents.model.Task.TaskCallbacks; -import com.android.systemui.shared.system.ActivityManagerWrapper; - -import java.util.ArrayDeque; -import java.util.ArrayList; - -/** - * Loader class that loads full-resolution thumbnails when appropriate. - */ -public class HighResThumbnailLoader implements TaskCallbacks { - - private final ActivityManagerWrapper mActivityManager; - - @GuardedBy("mLoadQueue") - private final ArrayDeque<Task> mLoadQueue = new ArrayDeque<>(); - @GuardedBy("mLoadQueue") - private final ArraySet<Task> mLoadingTasks = new ArraySet<>(); - @GuardedBy("mLoadQueue") - private boolean mLoaderIdling; - - private final ArrayList<Task> mVisibleTasks = new ArrayList<>(); - - private final Thread mLoadThread; - private final Handler mMainThreadHandler; - private final boolean mIsLowRamDevice; - private boolean mLoading; - private boolean mVisible; - private boolean mFlingingFast; - private boolean mTaskLoadQueueIdle; - - public HighResThumbnailLoader(ActivityManagerWrapper activityManager, Looper looper, - boolean isLowRamDevice) { - mActivityManager = activityManager; - mMainThreadHandler = new Handler(looper); - mLoadThread = new Thread(mLoader, "Recents-HighResThumbnailLoader"); - mLoadThread.start(); - mIsLowRamDevice = isLowRamDevice; - } - - public void setVisible(boolean visible) { - if (mIsLowRamDevice) { - return; - } - mVisible = visible; - updateLoading(); - } - - public void setFlingingFast(boolean flingingFast) { - if (mFlingingFast == flingingFast || mIsLowRamDevice) { - return; - } - mFlingingFast = flingingFast; - updateLoading(); - } - - /** - * Sets whether the other task load queue is idling. Avoid double-loading bitmaps by not - * starting this queue until the other queue is idling. - */ - public void setTaskLoadQueueIdle(boolean idle) { - if (mIsLowRamDevice) { - return; - } - mTaskLoadQueueIdle = idle; - updateLoading(); - } - - @VisibleForTesting - boolean isLoading() { - return mLoading; - } - - private void updateLoading() { - setLoading(mVisible && !mFlingingFast && mTaskLoadQueueIdle); - } - - private void setLoading(boolean loading) { - if (loading == mLoading) { - return; - } - synchronized (mLoadQueue) { - mLoading = loading; - if (!loading) { - stopLoading(); - } else { - startLoading(); - } - } - } - - @GuardedBy("mLoadQueue") - private void startLoading() { - for (int i = mVisibleTasks.size() - 1; i >= 0; i--) { - Task t = mVisibleTasks.get(i); - if ((t.thumbnail == null || t.thumbnail.reducedResolution) - && !mLoadQueue.contains(t) && !mLoadingTasks.contains(t)) { - mLoadQueue.add(t); - } - } - mLoadQueue.notifyAll(); - } - - @GuardedBy("mLoadQueue") - private void stopLoading() { - mLoadQueue.clear(); - mLoadQueue.notifyAll(); - } - - /** - * Needs to be called when a task becomes visible. Note that this is different from - * {@link TaskCallbacks#onTaskDataLoaded} as this method should only be called once when it - * becomes visible, whereas onTaskDataLoaded can be called multiple times whenever some data - * has been updated. - */ - public void onTaskVisible(Task t) { - t.addCallback(this); - mVisibleTasks.add(t); - if ((t.thumbnail == null || t.thumbnail.reducedResolution) && mLoading) { - synchronized (mLoadQueue) { - mLoadQueue.add(t); - mLoadQueue.notifyAll(); - } - } - } - - /** - * Needs to be called when a task becomes visible. See {@link #onTaskVisible} why this is - * different from {@link TaskCallbacks#onTaskDataUnloaded()} - */ - public void onTaskInvisible(Task t) { - t.removeCallback(this); - mVisibleTasks.remove(t); - synchronized (mLoadQueue) { - mLoadQueue.remove(t); - } - } - - @VisibleForTesting - void waitForLoaderIdle() { - while (true) { - synchronized (mLoadQueue) { - if (mLoadQueue.isEmpty() && mLoaderIdling) { - return; - } - } - SystemClock.sleep(100); - } - } - - @Override - public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData) { - if (thumbnailData != null && !thumbnailData.reducedResolution) { - synchronized (mLoadQueue) { - mLoadQueue.remove(task); - } - } - } - - @Override - public void onTaskDataUnloaded() { - } - - @Override - public void onTaskWindowingModeChanged() { - } - - private final Runnable mLoader = new Runnable() { - - @Override - public void run() { - setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND + 1); - while (true) { - Task next = null; - synchronized (mLoadQueue) { - if (!mLoading || mLoadQueue.isEmpty()) { - try { - mLoaderIdling = true; - mLoadQueue.wait(); - mLoaderIdling = false; - } catch (InterruptedException e) { - // Don't care. - } - } else { - next = mLoadQueue.poll(); - if (next != null) { - mLoadingTasks.add(next); - } - } - } - if (next != null) { - loadTask(next); - } - } - } - - private void loadTask(Task t) { - ThumbnailData thumbnail = mActivityManager.getTaskThumbnail(t.key.id, - false /* reducedResolution */); - mMainThreadHandler.post(() -> { - synchronized (mLoadQueue) { - mLoadingTasks.remove(t); - } - if (mVisibleTasks.contains(t)) { - t.notifyTaskDataLoaded(thumbnail, t.icon); - } - }); - } - }; -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java deleted file mode 100644 index a04a6a3fe0c5..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (C) 2014 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.shared.recents.model; - -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; - -import android.app.ActivityManager; -import android.app.KeyguardManager; -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; -import android.content.res.Resources; -import android.graphics.drawable.Drawable; -import android.util.SparseBooleanArray; - -import com.android.systemui.shared.recents.model.Task.TaskKey; -import com.android.systemui.shared.system.ActivityManagerWrapper; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - - -/** - * This class stores the loading state as it goes through multiple stages of loading: - * 1) preloadRawTasks() will load the raw set of recents tasks from the system - * 2) preloadPlan() will construct a new task stack with all metadata and only icons and - * thumbnails that are currently in the cache - * 3) executePlan() will actually load and fill in the icons and thumbnails according to the load - * options specified, such that we can transition into the Recents activity seamlessly - */ -public class RecentsTaskLoadPlan { - - /** The set of conditions to preload tasks. */ - public static class PreloadOptions { - public boolean loadTitles = true; - } - - /** The set of conditions to load tasks. */ - public static class Options { - public int runningTaskId = -1; - public boolean loadIcons = true; - public boolean loadThumbnails = false; - public boolean onlyLoadForCache = false; - public boolean onlyLoadPausedActivities = false; - public int numVisibleTasks = 0; - public int numVisibleTaskThumbnails = 0; - } - - private final Context mContext; - private final KeyguardManager mKeyguardManager; - - private List<ActivityManager.RecentTaskInfo> mRawTasks; - private TaskStack mStack; - - private final SparseBooleanArray mTmpLockedUsers = new SparseBooleanArray(); - - public RecentsTaskLoadPlan(Context context) { - mContext = context; - mKeyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); - } - - /** - * Preloads the list of recent tasks from the system. After this call, the TaskStack will - * have a list of all the recent tasks with their metadata, not including icons or - * thumbnails which were not cached and have to be loaded. - * - * The tasks will be ordered by: - * - least-recent to most-recent stack tasks - * - * Note: Do not lock, since this can be calling back to the loader, which separately also drives - * this call (callers should synchronize on the loader before making this call). - */ - public void preloadPlan(PreloadOptions opts, RecentsTaskLoader loader, int runningTaskId, - int currentUserId) { - Resources res = mContext.getResources(); - ArrayList<Task> allTasks = new ArrayList<>(); - if (mRawTasks == null) { - mRawTasks = ActivityManagerWrapper.getInstance().getRecentTasks( - ActivityManager.getMaxRecentTasksStatic(), currentUserId); - - // Since the raw tasks are given in most-recent to least-recent order, we need to reverse it - Collections.reverse(mRawTasks); - } - - int taskCount = mRawTasks.size(); - for (int i = 0; i < taskCount; i++) { - ActivityManager.RecentTaskInfo t = mRawTasks.get(i); - - // Compose the task key - final ComponentName sourceComponent = t.origActivity != null - // Activity alias if there is one - ? t.origActivity - // The real activity if there is no alias (or the target if there is one) - : t.realActivity; - final int windowingMode = t.configuration.windowConfiguration.getWindowingMode(); - TaskKey taskKey = new TaskKey(t.persistentId, windowingMode, t.baseIntent, - sourceComponent, t.userId, t.lastActiveTime); - - boolean isFreeformTask = windowingMode == WINDOWING_MODE_FREEFORM; - boolean isStackTask = !isFreeformTask; - boolean isLaunchTarget = taskKey.id == runningTaskId; - - ActivityInfo info = loader.getAndUpdateActivityInfo(taskKey); - if (info == null) { - continue; - } - - // Load the title, icon, and color - String title = opts.loadTitles - ? loader.getAndUpdateActivityTitle(taskKey, t.taskDescription) - : ""; - String titleDescription = opts.loadTitles - ? loader.getAndUpdateContentDescription(taskKey, t.taskDescription) - : ""; - Drawable icon = isStackTask - ? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, false) - : null; - ThumbnailData thumbnail = loader.getAndUpdateThumbnail(taskKey, - false /* loadIfNotCached */, false /* storeInCache */); - int activityColor = loader.getActivityPrimaryColor(t.taskDescription); - int backgroundColor = loader.getActivityBackgroundColor(t.taskDescription); - boolean isSystemApp = (info != null) && - ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0); - - // TODO: Refactor to not do this every preload - if (mTmpLockedUsers.indexOfKey(t.userId) < 0) { - mTmpLockedUsers.put(t.userId, mKeyguardManager.isDeviceLocked(t.userId)); - } - boolean isLocked = mTmpLockedUsers.get(t.userId); - - // Add the task to the stack - Task task = new Task(taskKey, icon, - thumbnail, title, titleDescription, activityColor, backgroundColor, - isLaunchTarget, isStackTask, isSystemApp, t.supportsSplitScreenMultiWindow, - t.taskDescription, t.resizeMode, t.topActivity, isLocked); - - allTasks.add(task); - } - - // Initialize the stacks - mStack = new TaskStack(); - mStack.setTasks(allTasks, false /* notifyStackChanges */); - } - - /** - * Called to apply the actual loading based on the specified conditions. - * - * Note: Do not lock, since this can be calling back to the loader, which separately also drives - * this call (callers should synchronize on the loader before making this call). - */ - public void executePlan(Options opts, RecentsTaskLoader loader) { - Resources res = mContext.getResources(); - - // Iterate through each of the tasks and load them according to the load conditions. - ArrayList<Task> tasks = mStack.getTasks(); - int taskCount = tasks.size(); - for (int i = 0; i < taskCount; i++) { - Task task = tasks.get(i); - TaskKey taskKey = task.key; - - boolean isRunningTask = (task.key.id == opts.runningTaskId); - boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks); - boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails); - - // If requested, skip the running task - if (opts.onlyLoadPausedActivities && isRunningTask) { - continue; - } - - if (opts.loadIcons && (isRunningTask || isVisibleTask)) { - if (task.icon == null) { - task.icon = loader.getAndUpdateActivityIcon(taskKey, task.taskDescription, - true); - } - } - if (opts.loadThumbnails && isVisibleThumbnail) { - task.thumbnail = loader.getAndUpdateThumbnail(taskKey, - true /* loadIfNotCached */, true /* storeInCache */); - } - } - } - - /** - * Returns the TaskStack from the preloaded list of recent tasks. - */ - public TaskStack getTaskStack() { - return mStack; - } - - /** Returns whether there are any tasks in any stacks. */ - public boolean hasTasks() { - if (mStack != null) { - return mStack.getTaskCount() > 0; - } - return false; - } -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/RecentsTaskLoader.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/RecentsTaskLoader.java deleted file mode 100644 index 1309a6065c8a..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/RecentsTaskLoader.java +++ /dev/null @@ -1,417 +0,0 @@ -/* - * Copyright (C) 2014 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.shared.recents.model; - -import android.app.ActivityManager; -import android.content.ComponentCallbacks2; -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.ActivityInfo; -import android.graphics.drawable.Drawable; -import android.os.Looper; -import android.os.Trace; -import android.util.Log; -import android.util.LruCache; - -import com.android.internal.annotations.GuardedBy; -import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.Options; -import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.PreloadOptions; -import com.android.systemui.shared.recents.model.Task.TaskKey; -import com.android.systemui.shared.recents.model.TaskKeyLruCache.EvictionCallback; -import com.android.systemui.shared.system.ActivityManagerWrapper; - -import java.io.PrintWriter; -import java.util.Map; - - -/** - * Recents task loader - */ -public class RecentsTaskLoader { - private static final String TAG = "RecentsTaskLoader"; - private static final boolean DEBUG = false; - - /** Levels of svelte in increasing severity/austerity. */ - // No svelting. - public static final int SVELTE_NONE = 0; - // Limit thumbnail cache to number of visible thumbnails when Recents was loaded, disable - // caching thumbnails as you scroll. - public static final int SVELTE_LIMIT_CACHE = 1; - // Disable the thumbnail cache, load thumbnails asynchronously when the activity loads and - // evict all thumbnails when hidden. - public static final int SVELTE_DISABLE_CACHE = 2; - // Disable all thumbnail loading. - public static final int SVELTE_DISABLE_LOADING = 3; - - // This activity info LruCache is useful because it can be expensive to retrieve ActivityInfos - // for many tasks, which we use to get the activity labels and icons. Unlike the other caches - // below, this is per-package so we can't invalidate the items in the cache based on the last - // active time. Instead, we rely on the PackageMonitor to keep us informed whenever a - // package in the cache has been updated, so that we may remove it. - private final LruCache<ComponentName, ActivityInfo> mActivityInfoCache; - private final TaskKeyLruCache<Drawable> mIconCache; - private final TaskKeyLruCache<String> mActivityLabelCache; - private final TaskKeyLruCache<String> mContentDescriptionCache; - private final TaskResourceLoadQueue mLoadQueue; - private final IconLoader mIconLoader; - private final BackgroundTaskLoader mLoader; - private final HighResThumbnailLoader mHighResThumbnailLoader; - @GuardedBy("this") - private final TaskKeyStrongCache<ThumbnailData> mThumbnailCache = new TaskKeyStrongCache<>(); - @GuardedBy("this") - private final TaskKeyStrongCache<ThumbnailData> mTempCache = new TaskKeyStrongCache<>(); - private final int mMaxThumbnailCacheSize; - private final int mMaxIconCacheSize; - private int mNumVisibleTasksLoaded; - private int mSvelteLevel; - - private int mDefaultTaskBarBackgroundColor; - private int mDefaultTaskViewBackgroundColor; - - private EvictionCallback mClearActivityInfoOnEviction = new EvictionCallback() { - @Override - public void onEntryEvicted(TaskKey key) { - if (key != null) { - mActivityInfoCache.remove(key.getComponent()); - } - } - }; - - public RecentsTaskLoader(Context context, int maxThumbnailCacheSize, int maxIconCacheSize, - int svelteLevel) { - mMaxThumbnailCacheSize = maxThumbnailCacheSize; - mMaxIconCacheSize = maxIconCacheSize; - mSvelteLevel = svelteLevel; - - // Initialize the proxy, cache and loaders - int numRecentTasks = ActivityManager.getMaxRecentTasksStatic(); - mHighResThumbnailLoader = new HighResThumbnailLoader(ActivityManagerWrapper.getInstance(), - Looper.getMainLooper(), ActivityManager.isLowRamDeviceStatic()); - mLoadQueue = new TaskResourceLoadQueue(); - mIconCache = new TaskKeyLruCache<>(mMaxIconCacheSize, mClearActivityInfoOnEviction); - mActivityLabelCache = new TaskKeyLruCache<>(numRecentTasks, mClearActivityInfoOnEviction); - mContentDescriptionCache = new TaskKeyLruCache<>(numRecentTasks, - mClearActivityInfoOnEviction); - mActivityInfoCache = new LruCache<>(numRecentTasks); - - mIconLoader = createNewIconLoader(context, mIconCache, mActivityInfoCache); - mLoader = new BackgroundTaskLoader(mLoadQueue, mIconLoader, - mHighResThumbnailLoader::setTaskLoadQueueIdle); - } - - protected IconLoader createNewIconLoader(Context context,TaskKeyLruCache<Drawable> iconCache, - LruCache<ComponentName, ActivityInfo> activityInfoCache) { - return new IconLoader.DefaultIconLoader(context, iconCache, activityInfoCache); - } - - /** - * Sets the default task bar/view colors if none are provided by the app. - */ - public void setDefaultColors(int defaultTaskBarBackgroundColor, - int defaultTaskViewBackgroundColor) { - mDefaultTaskBarBackgroundColor = defaultTaskBarBackgroundColor; - mDefaultTaskViewBackgroundColor = defaultTaskViewBackgroundColor; - } - - /** Returns the size of the app icon cache. */ - public int getIconCacheSize() { - return mMaxIconCacheSize; - } - - /** Returns the size of the thumbnail cache. */ - public int getThumbnailCacheSize() { - return mMaxThumbnailCacheSize; - } - - public HighResThumbnailLoader getHighResThumbnailLoader() { - return mHighResThumbnailLoader; - } - - /** Preloads recents tasks using the specified plan to store the output. */ - public synchronized void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId) { - preloadTasks(plan, runningTaskId, ActivityManagerWrapper.getInstance().getCurrentUserId()); - } - - /** Preloads recents tasks using the specified plan to store the output. */ - public synchronized void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId, - int currentUserId) { - try { - Trace.beginSection("preloadPlan"); - plan.preloadPlan(new PreloadOptions(), this, runningTaskId, currentUserId); - } finally { - Trace.endSection(); - } - } - - /** Begins loading the heavy task data according to the specified options. */ - public synchronized void loadTasks(RecentsTaskLoadPlan plan, Options opts) { - if (opts == null) { - throw new RuntimeException("Requires load options"); - } - if (opts.onlyLoadForCache && opts.loadThumbnails) { - // If we are loading for the cache, we'd like to have the real cache only include the - // visible thumbnails. However, we also don't want to reload already cached thumbnails. - // Thus, we copy over the current entries into a second cache, and clear the real cache, - // such that the real cache only contains visible thumbnails. - mTempCache.copyEntries(mThumbnailCache); - mThumbnailCache.evictAll(); - } - plan.executePlan(opts, this); - mTempCache.evictAll(); - if (!opts.onlyLoadForCache) { - mNumVisibleTasksLoaded = opts.numVisibleTasks; - } - } - - /** - * Acquires the task resource data directly from the cache, loading if necessary. - */ - public void loadTaskData(Task t) { - Drawable icon = mIconCache.getAndInvalidateIfModified(t.key); - icon = icon != null ? icon : mIconLoader.getDefaultIcon(t.key.userId); - mLoadQueue.addTask(t); - t.notifyTaskDataLoaded(t.thumbnail, icon); - } - - /** Releases the task resource data back into the pool. */ - public void unloadTaskData(Task t) { - mLoadQueue.removeTask(t); - t.notifyTaskDataUnloaded(mIconLoader.getDefaultIcon(t.key.userId)); - } - - /** Completely removes the resource data from the pool. */ - public void deleteTaskData(Task t, boolean notifyTaskDataUnloaded) { - mLoadQueue.removeTask(t); - mIconCache.remove(t.key); - mActivityLabelCache.remove(t.key); - mContentDescriptionCache.remove(t.key); - if (notifyTaskDataUnloaded) { - t.notifyTaskDataUnloaded(mIconLoader.getDefaultIcon(t.key.userId)); - } - } - - /** - * Handles signals from the system, trimming memory when requested to prevent us from running - * out of memory. - */ - public synchronized void onTrimMemory(int level) { - switch (level) { - case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN: - // Stop the loader immediately when the UI is no longer visible - stopLoader(); - mIconCache.trimToSize(Math.max(mNumVisibleTasksLoaded, - mMaxIconCacheSize / 2)); - break; - case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE: - case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND: - // We are leaving recents, so trim the data a bit - mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 2)); - mActivityInfoCache.trimToSize(Math.max(1, - ActivityManager.getMaxRecentTasksStatic() / 2)); - break; - case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW: - case ComponentCallbacks2.TRIM_MEMORY_MODERATE: - // We are going to be low on memory - mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 4)); - mActivityInfoCache.trimToSize(Math.max(1, - ActivityManager.getMaxRecentTasksStatic() / 4)); - break; - case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL: - case ComponentCallbacks2.TRIM_MEMORY_COMPLETE: - // We are low on memory, so release everything - mIconCache.evictAll(); - mActivityInfoCache.evictAll(); - // The cache is small, only clear the label cache when we are critical - mActivityLabelCache.evictAll(); - mContentDescriptionCache.evictAll(); - mThumbnailCache.evictAll(); - break; - default: - break; - } - } - - public void onPackageChanged(String packageName) { - // Remove all the cached activity infos for this package. The other caches do not need to - // be pruned at this time, as the TaskKey expiration checks will flush them next time their - // cached contents are requested - Map<ComponentName, ActivityInfo> activityInfoCache = mActivityInfoCache.snapshot(); - for (ComponentName cn : activityInfoCache.keySet()) { - if (cn.getPackageName().equals(packageName)) { - if (DEBUG) { - Log.d(TAG, "Removing activity info from cache: " + cn); - } - mActivityInfoCache.remove(cn); - } - } - } - - /** - * Returns the cached task label if the task key is not expired, updating the cache if it is. - */ - String getAndUpdateActivityTitle(TaskKey taskKey, ActivityManager.TaskDescription td) { - // Return the task description label if it exists - if (td != null && td.getLabel() != null) { - return td.getLabel(); - } - // Return the cached activity label if it exists - String label = mActivityLabelCache.getAndInvalidateIfModified(taskKey); - if (label != null) { - return label; - } - // All short paths failed, load the label from the activity info and cache it - ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey); - if (activityInfo != null) { - label = ActivityManagerWrapper.getInstance().getBadgedActivityLabel(activityInfo, - taskKey.userId); - mActivityLabelCache.put(taskKey, label); - return label; - } - // If the activity info does not exist or fails to load, return an empty label for now, - // but do not cache it - return ""; - } - - /** - * Returns the cached task content description if the task key is not expired, updating the - * cache if it is. - */ - String getAndUpdateContentDescription(TaskKey taskKey, ActivityManager.TaskDescription td) { - // Return the cached content description if it exists - String label = mContentDescriptionCache.getAndInvalidateIfModified(taskKey); - if (label != null) { - return label; - } - - // All short paths failed, load the label from the activity info and cache it - ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey); - if (activityInfo != null) { - label = ActivityManagerWrapper.getInstance().getBadgedContentDescription( - activityInfo, taskKey.userId, td); - if (td == null) { - // Only add to the cache if the task description is null, otherwise, it is possible - // for the task description to change between calls without the last active time - // changing (ie. between preloading and Overview starting) which would lead to stale - // content descriptions - // TODO: Investigate improving this - mContentDescriptionCache.put(taskKey, label); - } - return label; - } - // If the content description does not exist, return an empty label for now, but do not - // cache it - return ""; - } - - /** - * Returns the cached task icon if the task key is not expired, updating the cache if it is. - */ - Drawable getAndUpdateActivityIcon(TaskKey taskKey, ActivityManager.TaskDescription td, - boolean loadIfNotCached) { - return mIconLoader.getAndInvalidateIfModified(taskKey, td, loadIfNotCached); - } - - /** - * Returns the cached thumbnail if the task key is not expired, updating the cache if it is. - */ - synchronized ThumbnailData getAndUpdateThumbnail(TaskKey taskKey, boolean loadIfNotCached, - boolean storeInCache) { - ThumbnailData cached = mThumbnailCache.getAndInvalidateIfModified(taskKey); - if (cached != null) { - return cached; - } - - cached = mTempCache.getAndInvalidateIfModified(taskKey); - if (cached != null) { - mThumbnailCache.put(taskKey, cached); - return cached; - } - - if (loadIfNotCached) { - if (mSvelteLevel < SVELTE_DISABLE_LOADING) { - // Load the thumbnail from the system - ThumbnailData thumbnailData = ActivityManagerWrapper.getInstance().getTaskThumbnail( - taskKey.id, true /* reducedResolution */); - if (thumbnailData.thumbnail != null) { - if (storeInCache) { - mThumbnailCache.put(taskKey, thumbnailData); - } - return thumbnailData; - } - } - } - - // We couldn't load any thumbnail - return null; - } - - /** - * Returns the task's primary color if possible, defaulting to the default color if there is - * no specified primary color. - */ - int getActivityPrimaryColor(ActivityManager.TaskDescription td) { - if (td != null && td.getPrimaryColor() != 0) { - return td.getPrimaryColor(); - } - return mDefaultTaskBarBackgroundColor; - } - - /** - * Returns the task's background color if possible. - */ - int getActivityBackgroundColor(ActivityManager.TaskDescription td) { - if (td != null && td.getBackgroundColor() != 0) { - return td.getBackgroundColor(); - } - return mDefaultTaskViewBackgroundColor; - } - - /** - * Returns the activity info for the given task key, retrieving one from the system if the - * task key is expired. - */ - ActivityInfo getAndUpdateActivityInfo(TaskKey taskKey) { - return mIconLoader.getAndUpdateActivityInfo(taskKey); - } - - /** - * Starts loading tasks. - */ - public void startLoader(Context ctx) { - mLoader.start(ctx); - } - - /** - * Stops the task loader and clears all queued, pending task loads. - */ - private void stopLoader() { - mLoader.stop(); - mLoadQueue.clearTasks(); - } - - public synchronized void dump(String prefix, PrintWriter writer) { - String innerPrefix = prefix + " "; - - writer.print(prefix); writer.println(TAG); - writer.print(prefix); writer.println("Icon Cache"); - mIconCache.dump(innerPrefix, writer); - writer.print(prefix); writer.println("Thumbnail Cache"); - mThumbnailCache.dump(innerPrefix, writer); - writer.print(prefix); writer.println("Temp Thumbnail Cache"); - mTempCache.dump(innerPrefix, writer); - } -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java index b51004bf6d56..7558efae6c23 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java @@ -16,6 +16,7 @@ package com.android.systemui.shared.recents.model; +import android.app.ActivityManager; import android.app.ActivityManager.TaskDescription; import android.content.ComponentName; import android.content.Intent; @@ -31,13 +32,14 @@ import java.util.ArrayList; import java.util.Objects; /** - * A task represents the top most task in the system's task stack. + * A task in the recent tasks list. */ public class Task { public static final String TAG = "Task"; /* Task callbacks */ + @Deprecated public interface TaskCallbacks { /* Notifies when a task has been bound */ void onTaskDataLoaded(Task task, ThumbnailData thumbnailData); @@ -65,6 +67,21 @@ public class Task { private int mHashCode; + public TaskKey(ActivityManager.RecentTaskInfo t) { + ComponentName sourceComponent = t.origActivity != null + // Activity alias if there is one + ? t.origActivity + // The real activity if there is no alias (or the target if there is one) + : t.realActivity; + this.id = t.taskId; + this.windowingMode = t.configuration.windowConfiguration.getWindowingMode(); + this.baseIntent = t.baseIntent; + this.sourceComponent = sourceComponent; + this.userId = t.userId; + this.lastActiveTime = t.lastActiveTime; + updateHashCode(); + } + public TaskKey(int id, int windowingMode, Intent intent, ComponentName sourceComponent, int userId, long lastActiveTime) { this.id = id; @@ -125,6 +142,7 @@ public class Task { /** * The temporary sort index in the stack, used when ordering the stack. */ + @Deprecated public int temporarySortIndexInStack; /** @@ -134,6 +152,7 @@ public class Task { public Drawable icon; public ThumbnailData thumbnail; @ViewDebug.ExportedProperty(category="recents") + @Deprecated public String title; @ViewDebug.ExportedProperty(category="recents") public String titleDescription; @@ -142,6 +161,7 @@ public class Task { @ViewDebug.ExportedProperty(category="recents") public int colorBackground; @ViewDebug.ExportedProperty(category="recents") + @Deprecated public boolean useLightOnPrimaryColor; /** @@ -153,10 +173,13 @@ public class Task { * The state isLaunchTarget will be set for the correct task upon launching Recents. */ @ViewDebug.ExportedProperty(category="recents") + @Deprecated public boolean isLaunchTarget; @ViewDebug.ExportedProperty(category="recents") + @Deprecated public boolean isStackTask; @ViewDebug.ExportedProperty(category="recents") + @Deprecated public boolean isSystemApp; @ViewDebug.ExportedProperty(category="recents") public boolean isDockable; @@ -165,6 +188,7 @@ public class Task { * Resize mode. See {@link ActivityInfo#resizeMode}. */ @ViewDebug.ExportedProperty(category="recents") + @Deprecated public int resizeMode; @ViewDebug.ExportedProperty(category="recents") @@ -173,12 +197,31 @@ public class Task { @ViewDebug.ExportedProperty(category="recents") public boolean isLocked; + @Deprecated private ArrayList<TaskCallbacks> mCallbacks = new ArrayList<>(); public Task() { // Do nothing } + public Task(TaskKey key) { + this.key = key; + this.taskDescription = new TaskDescription(); + } + + public Task(TaskKey key, int colorPrimary, int colorBackground, + boolean isDockable, boolean isLocked, TaskDescription taskDescription, + ComponentName topActivity) { + this.key = key; + this.colorPrimary = colorPrimary; + this.colorBackground = colorBackground; + this.taskDescription = taskDescription; + this.isDockable = isDockable; + this.isLocked = isLocked; + this.topActivity = topActivity; + } + + @Deprecated public Task(TaskKey key, Drawable icon, ThumbnailData thumbnail, String title, String titleDescription, int colorPrimary, int colorBackground, boolean isLaunchTarget, boolean isStackTask, boolean isSystemApp, boolean isDockable, @@ -206,6 +249,7 @@ public class Task { /** * Copies the metadata from another task, but retains the current callbacks. */ + @Deprecated public void copyFrom(Task o) { this.key = o.key; this.icon = o.icon; @@ -228,6 +272,7 @@ public class Task { /** * Add a callback. */ + @Deprecated public void addCallback(TaskCallbacks cb) { if (!mCallbacks.contains(cb)) { mCallbacks.add(cb); @@ -237,11 +282,13 @@ public class Task { /** * Remove a callback. */ + @Deprecated public void removeCallback(TaskCallbacks cb) { mCallbacks.remove(cb); } /** Updates the task's windowing mode. */ + @Deprecated public void setWindowingMode(int windowingMode) { key.setWindowingMode(windowingMode); int callbackCount = mCallbacks.size(); @@ -251,6 +298,7 @@ public class Task { } /** Notifies the callback listeners that this task has been loaded */ + @Deprecated public void notifyTaskDataLoaded(ThumbnailData thumbnailData, Drawable applicationIcon) { this.icon = applicationIcon; this.thumbnail = thumbnailData; @@ -261,6 +309,7 @@ public class Task { } /** Notifies the callback listeners that this task has been unloaded */ + @Deprecated public void notifyTaskDataUnloaded(Drawable defaultApplicationIcon) { icon = defaultApplicationIcon; thumbnail = null; diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskFilter.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskFilter.java deleted file mode 100644 index 5f3dcd16e074..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskFilter.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2017 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.shared.recents.model; - -import android.util.SparseArray; - -/** - * An interface for a task filter to query whether a particular task should show in a stack. - */ -public interface TaskFilter { - /** Returns whether the filter accepts the specified task */ - boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index); -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyCache.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyCache.java index 4bf3500a3405..342cb75b2c14 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyCache.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyCache.java @@ -34,7 +34,7 @@ public abstract class TaskKeyCache<V> { * Gets a specific entry in the cache with the specified key, regardless of whether the cached * value is valid or not. */ - final V get(TaskKey key) { + public final synchronized V get(TaskKey key) { return getCacheEntry(key.id); } @@ -42,7 +42,7 @@ public abstract class TaskKeyCache<V> { * Returns the value only if the key is valid (has not been updated since the last time it was * in the cache) */ - final V getAndInvalidateIfModified(TaskKey key) { + public final synchronized V getAndInvalidateIfModified(TaskKey key) { TaskKey lastKey = mKeys.get(key.id); if (lastKey != null) { if ((lastKey.windowingMode != key.windowingMode) || @@ -59,7 +59,7 @@ public abstract class TaskKeyCache<V> { } /** Puts an entry in the cache for a specific key. */ - final void put(TaskKey key, V value) { + public final synchronized void put(TaskKey key, V value) { if (key == null || value == null) { Log.e(TAG, "Unexpected null key or value: " + key + ", " + value); return; @@ -70,14 +70,14 @@ public abstract class TaskKeyCache<V> { /** Removes a cache entry for a specific key. */ - final void remove(TaskKey key) { + public final synchronized void remove(TaskKey key) { // Remove the key after the cache value because we need it to make the callback removeCacheEntry(key.id); mKeys.remove(key.id); } /** Removes all the entries in the cache. */ - final void evictAll() { + public final synchronized void evictAll() { evictAllCache(); mKeys.clear(); } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyLruCache.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyLruCache.java index 0ba2c3bf6e3c..b2c79a4c0a32 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyLruCache.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyLruCache.java @@ -58,7 +58,7 @@ public class TaskKeyLruCache<V> extends TaskKeyCache<V> { } /** Trims the cache to a specific size */ - final void trimToSize(int cacheSize) { + public final void trimToSize(int cacheSize) { mCache.trimToSize(cacheSize); } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyStrongCache.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyStrongCache.java deleted file mode 100644 index 4408eced3e93..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyStrongCache.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2017 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.shared.recents.model; - -import android.util.ArrayMap; - -import com.android.systemui.shared.recents.model.Task.TaskKey; - -import java.io.PrintWriter; - -/** - * Like {@link TaskKeyLruCache}, but without LRU functionality. - */ -public class TaskKeyStrongCache<V> extends TaskKeyCache<V> { - - private static final String TAG = "TaskKeyCache"; - - private final ArrayMap<Integer, V> mCache = new ArrayMap<>(); - - final void copyEntries(TaskKeyStrongCache<V> other) { - for (int i = other.mKeys.size() - 1; i >= 0; i--) { - TaskKey key = other.mKeys.valueAt(i); - put(key, other.mCache.get(key.id)); - } - } - - public void dump(String prefix, PrintWriter writer) { - String innerPrefix = prefix + " "; - writer.print(prefix); writer.print(TAG); - writer.print(" numEntries="); writer.print(mKeys.size()); - writer.println(); - int keyCount = mKeys.size(); - for (int i = 0; i < keyCount; i++) { - writer.print(innerPrefix); writer.println(mKeys.get(mKeys.keyAt(i))); - } - } - - @Override - protected V getCacheEntry(int id) { - return mCache.get(id); - } - - @Override - protected void putCacheEntry(int id, V value) { - mCache.put(id, value); - } - - @Override - protected void removeCacheEntry(int id) { - mCache.remove(id); - } - - @Override - protected void evictAllCache() { - mCache.clear(); - } -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskResourceLoadQueue.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskResourceLoadQueue.java deleted file mode 100644 index fbb6acebc8e0..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskResourceLoadQueue.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2017 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.shared.recents.model; - -import java.util.concurrent.ConcurrentLinkedQueue; - -/** - * A Task load queue - */ -class TaskResourceLoadQueue { - - private final ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<>(); - - /** Adds a new task to the load queue */ - void addTask(Task t) { - if (!mQueue.contains(t)) { - mQueue.add(t); - } - synchronized(this) { - notifyAll(); - } - } - - /** - * Retrieves the next task from the load queue, as well as whether we want that task to be - * force reloaded. - */ - Task nextTask() { - return mQueue.poll(); - } - - /** Removes a task from the load queue */ - void removeTask(Task t) { - mQueue.remove(t); - } - - /** Clears all the tasks from the load queue */ - void clearTasks() { - mQueue.clear(); - } - - /** Returns whether the load queue is empty */ - boolean isEmpty() { - return mQueue.isEmpty(); - } -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskStack.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskStack.java deleted file mode 100644 index a36939769477..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskStack.java +++ /dev/null @@ -1,395 +0,0 @@ -/* - * Copyright (C) 2014 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.shared.recents.model; - -import android.content.ComponentName; -import android.util.ArrayMap; -import android.util.ArraySet; - -import com.android.systemui.shared.recents.model.Task.TaskKey; -import com.android.systemui.shared.recents.utilities.AnimationProps; -import com.android.systemui.shared.system.PackageManagerWrapper; - -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.List; - - -/** - * The task stack contains a list of multiple tasks. - */ -public class TaskStack { - - private static final String TAG = "TaskStack"; - - /** Task stack callbacks */ - public interface TaskStackCallbacks { - /** - * Notifies when a new task has been added to the stack. - */ - void onStackTaskAdded(TaskStack stack, Task newTask); - - /** - * Notifies when a task has been removed from the stack. - */ - void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask, - AnimationProps animation, boolean fromDockGesture, - boolean dismissRecentsIfAllRemoved); - - /** - * Notifies when all tasks have been removed from the stack. - */ - void onStackTasksRemoved(TaskStack stack); - - /** - * Notifies when tasks in the stack have been updated. - */ - void onStackTasksUpdated(TaskStack stack); - } - - private final ArrayList<Task> mRawTaskList = new ArrayList<>(); - private final FilteredTaskList mStackTaskList = new FilteredTaskList(); - private TaskStackCallbacks mCb; - - public TaskStack() { - // Ensure that we only show stack tasks - mStackTaskList.setFilter((taskIdMap, t, index) -> t.isStackTask); - } - - /** Sets the callbacks for this task stack. */ - public void setCallbacks(TaskStackCallbacks cb) { - mCb = cb; - } - - /** - * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on - * how they should update themselves. - */ - public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture) { - removeTask(t, animation, fromDockGesture, true /* dismissRecentsIfAllRemoved */); - } - - /** - * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on - * how they should update themselves. - */ - public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture, - boolean dismissRecentsIfAllRemoved) { - if (mStackTaskList.contains(t)) { - mStackTaskList.remove(t); - Task newFrontMostTask = getFrontMostTask(); - if (mCb != null) { - // Notify that a task has been removed - mCb.onStackTaskRemoved(this, t, newFrontMostTask, animation, - fromDockGesture, dismissRecentsIfAllRemoved); - } - } - mRawTaskList.remove(t); - } - - /** - * Removes all tasks from the stack. - */ - public void removeAllTasks(boolean notifyStackChanges) { - ArrayList<Task> tasks = mStackTaskList.getTasks(); - for (int i = tasks.size() - 1; i >= 0; i--) { - Task t = tasks.get(i); - mStackTaskList.remove(t); - mRawTaskList.remove(t); - } - if (mCb != null && notifyStackChanges) { - // Notify that all tasks have been removed - mCb.onStackTasksRemoved(this); - } - } - - - /** - * @see #setTasks(List, boolean) - */ - public void setTasks(TaskStack stack, boolean notifyStackChanges) { - setTasks(stack.mRawTaskList, notifyStackChanges); - } - - /** - * Sets a few tasks in one go, without calling any callbacks. - * - * @param tasks the new set of tasks to replace the current set. - * @param notifyStackChanges whether or not to callback on specific changes to the list of tasks. - */ - public void setTasks(List<Task> tasks, boolean notifyStackChanges) { - // Compute a has set for each of the tasks - ArrayMap<TaskKey, Task> currentTasksMap = createTaskKeyMapFromList(mRawTaskList); - ArrayMap<TaskKey, Task> newTasksMap = createTaskKeyMapFromList(tasks); - ArrayList<Task> addedTasks = new ArrayList<>(); - ArrayList<Task> removedTasks = new ArrayList<>(); - ArrayList<Task> allTasks = new ArrayList<>(); - - // Disable notifications if there are no callbacks - if (mCb == null) { - notifyStackChanges = false; - } - - // Remove any tasks that no longer exist - int taskCount = mRawTaskList.size(); - for (int i = taskCount - 1; i >= 0; i--) { - Task task = mRawTaskList.get(i); - if (!newTasksMap.containsKey(task.key)) { - if (notifyStackChanges) { - removedTasks.add(task); - } - } - } - - // Add any new tasks - taskCount = tasks.size(); - for (int i = 0; i < taskCount; i++) { - Task newTask = tasks.get(i); - Task currentTask = currentTasksMap.get(newTask.key); - if (currentTask == null && notifyStackChanges) { - addedTasks.add(newTask); - } else if (currentTask != null) { - // The current task has bound callbacks, so just copy the data from the new task - // state and add it back into the list - currentTask.copyFrom(newTask); - newTask = currentTask; - } - allTasks.add(newTask); - } - - // Sort all the tasks to ensure they are ordered correctly - for (int i = allTasks.size() - 1; i >= 0; i--) { - allTasks.get(i).temporarySortIndexInStack = i; - } - - mStackTaskList.set(allTasks); - mRawTaskList.clear(); - mRawTaskList.addAll(allTasks); - - // Only callback for the removed tasks after the stack has updated - int removedTaskCount = removedTasks.size(); - Task newFrontMostTask = getFrontMostTask(); - for (int i = 0; i < removedTaskCount; i++) { - mCb.onStackTaskRemoved(this, removedTasks.get(i), newFrontMostTask, - AnimationProps.IMMEDIATE, false /* fromDockGesture */, - true /* dismissRecentsIfAllRemoved */); - } - - // Only callback for the newly added tasks after this stack has been updated - int addedTaskCount = addedTasks.size(); - for (int i = 0; i < addedTaskCount; i++) { - mCb.onStackTaskAdded(this, addedTasks.get(i)); - } - - // Notify that the task stack has been updated - if (notifyStackChanges) { - mCb.onStackTasksUpdated(this); - } - } - - /** - * Gets the front-most task in the stack. - */ - public Task getFrontMostTask() { - ArrayList<Task> stackTasks = mStackTaskList.getTasks(); - if (stackTasks.isEmpty()) { - return null; - } - return stackTasks.get(stackTasks.size() - 1); - } - - /** Gets the task keys */ - public ArrayList<TaskKey> getTaskKeys() { - ArrayList<TaskKey> taskKeys = new ArrayList<>(); - ArrayList<Task> tasks = computeAllTasksList(); - int taskCount = tasks.size(); - for (int i = 0; i < taskCount; i++) { - Task task = tasks.get(i); - taskKeys.add(task.key); - } - return taskKeys; - } - - /** - * Returns the set of "active" (non-historical) tasks in the stack that have been used recently. - */ - public ArrayList<Task> getTasks() { - return mStackTaskList.getTasks(); - } - - /** - * Computes a set of all the active and historical tasks. - */ - public ArrayList<Task> computeAllTasksList() { - ArrayList<Task> tasks = new ArrayList<>(); - tasks.addAll(mStackTaskList.getTasks()); - return tasks; - } - - /** - * Returns the number of stack tasks. - */ - public int getTaskCount() { - return mStackTaskList.size(); - } - - /** - * Returns the task in stack tasks which is the launch target. - */ - public Task getLaunchTarget() { - ArrayList<Task> tasks = mStackTaskList.getTasks(); - int taskCount = tasks.size(); - for (int i = 0; i < taskCount; i++) { - Task task = tasks.get(i); - if (task.isLaunchTarget) { - return task; - } - } - return null; - } - - /** - * Returns whether the next launch target should actually be the PiP task. - */ - public boolean isNextLaunchTargetPip(long lastPipTime) { - Task launchTarget = getLaunchTarget(); - Task nextLaunchTarget = getNextLaunchTargetRaw(); - if (nextLaunchTarget != null && lastPipTime > 0) { - // If the PiP time is more recent than the next launch target, then launch the PiP task - return lastPipTime > nextLaunchTarget.key.lastActiveTime; - } else if (launchTarget != null && lastPipTime > 0 && getTaskCount() == 1) { - // Otherwise, if there is no next launch target, but there is a PiP, then launch - // the PiP task - return true; - } - return false; - } - - /** - * Returns the task in stack tasks which should be launched next if Recents are toggled - * again, or null if there is no task to be launched. Callers should check - * {@link #isNextLaunchTargetPip(long)} before fetching the next raw launch target from the - * stack. - */ - public Task getNextLaunchTarget() { - Task nextLaunchTarget = getNextLaunchTargetRaw(); - if (nextLaunchTarget != null) { - return nextLaunchTarget; - } - return getTasks().get(getTaskCount() - 1); - } - - private Task getNextLaunchTargetRaw() { - int taskCount = getTaskCount(); - if (taskCount == 0) { - return null; - } - int launchTaskIndex = indexOfTask(getLaunchTarget()); - if (launchTaskIndex != -1 && launchTaskIndex > 0) { - return getTasks().get(launchTaskIndex - 1); - } - return null; - } - - /** Returns the index of this task in this current task stack */ - public int indexOfTask(Task t) { - return mStackTaskList.indexOf(t); - } - - /** Finds the task with the specified task id. */ - public Task findTaskWithId(int taskId) { - ArrayList<Task> tasks = computeAllTasksList(); - int taskCount = tasks.size(); - for (int i = 0; i < taskCount; i++) { - Task task = tasks.get(i); - if (task.key.id == taskId) { - return task; - } - } - return null; - } - - /** - * Computes the components of tasks in this stack that have been removed as a result of a change - * in the specified package. - */ - public ArraySet<ComponentName> computeComponentsRemoved(String packageName, int userId) { - // Identify all the tasks that should be removed as a result of the package being removed. - // Using a set to ensure that we callback once per unique component. - ArraySet<ComponentName> existingComponents = new ArraySet<>(); - ArraySet<ComponentName> removedComponents = new ArraySet<>(); - ArrayList<TaskKey> taskKeys = getTaskKeys(); - int taskKeyCount = taskKeys.size(); - for (int i = 0; i < taskKeyCount; i++) { - TaskKey t = taskKeys.get(i); - - // Skip if this doesn't apply to the current user - if (t.userId != userId) continue; - - ComponentName cn = t.getComponent(); - if (cn.getPackageName().equals(packageName)) { - if (existingComponents.contains(cn)) { - // If we know that the component still exists in the package, then skip - continue; - } - if (PackageManagerWrapper.getInstance().getActivityInfo(cn, userId) != null) { - existingComponents.add(cn); - } else { - removedComponents.add(cn); - } - } - } - return removedComponents; - } - - @Override - public String toString() { - String str = "Stack Tasks (" + mStackTaskList.size() + "):\n"; - ArrayList<Task> tasks = mStackTaskList.getTasks(); - int taskCount = tasks.size(); - for (int i = 0; i < taskCount; i++) { - str += " " + tasks.get(i).toString() + "\n"; - } - return str; - } - - /** - * Given a list of tasks, returns a map of each task's key to the task. - */ - private ArrayMap<TaskKey, Task> createTaskKeyMapFromList(List<Task> tasks) { - ArrayMap<TaskKey, Task> map = new ArrayMap<>(tasks.size()); - int taskCount = tasks.size(); - for (int i = 0; i < taskCount; i++) { - Task task = tasks.get(i); - map.put(task.key, task); - } - return map; - } - - public void dump(String prefix, PrintWriter writer) { - String innerPrefix = prefix + " "; - - writer.print(prefix); writer.print(TAG); - writer.print(" numStackTasks="); writer.print(mStackTaskList.size()); - writer.println(); - ArrayList<Task> tasks = mStackTaskList.getTasks(); - int taskCount = tasks.size(); - for (int i = 0; i < taskCount; i++) { - tasks.get(i).dump(innerPrefix, writer); - } - } -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/AnimationProps.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/AnimationProps.java deleted file mode 100644 index 2de7f74ba477..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/AnimationProps.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * 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.shared.recents.utilities; - -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.ValueAnimator; -import android.annotation.IntDef; -import android.util.SparseArray; -import android.util.SparseLongArray; -import android.view.View; -import android.view.animation.Interpolator; -import android.view.animation.LinearInterpolator; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.List; - -/** - * The generic set of animation properties to animate a {@link View}. The animation can have - * different interpolators, start delays and durations for each of the different properties. - */ -public class AnimationProps { - - private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); - public static final AnimationProps IMMEDIATE = new AnimationProps(0, LINEAR_INTERPOLATOR); - - @Retention(RetentionPolicy.SOURCE) - @IntDef({ALL, TRANSLATION_X, TRANSLATION_Y, TRANSLATION_Z, ALPHA, SCALE, BOUNDS}) - public @interface PropType {} - - public static final int ALL = 0; - public static final int TRANSLATION_X = 1; - public static final int TRANSLATION_Y = 2; - public static final int TRANSLATION_Z = 3; - public static final int ALPHA = 4; - public static final int SCALE = 5; - public static final int BOUNDS = 6; - public static final int DIM_ALPHA = 7; - - private SparseLongArray mPropStartDelay; - private SparseLongArray mPropDuration; - private SparseArray<Interpolator> mPropInterpolators; - private Animator.AnimatorListener mListener; - - /** - * The builder constructor. - */ - public AnimationProps() {} - - /** - * Creates an animation with a default {@param duration} and {@param interpolator} for all - * properties in this animation. - */ - public AnimationProps(int duration, Interpolator interpolator) { - this(0, duration, interpolator, null); - } - - /** - * Creates an animation with a default {@param duration} and {@param interpolator} for all - * properties in this animation. - */ - public AnimationProps(int duration, Interpolator interpolator, - Animator.AnimatorListener listener) { - this(0, duration, interpolator, listener); - } - - /** - * Creates an animation with a default {@param startDelay}, {@param duration} and - * {@param interpolator} for all properties in this animation. - */ - public AnimationProps(int startDelay, int duration, Interpolator interpolator) { - this(startDelay, duration, interpolator, null); - } - - /** - * Creates an animation with a default {@param startDelay}, {@param duration} and - * {@param interpolator} for all properties in this animation. - */ - public AnimationProps(int startDelay, int duration, Interpolator interpolator, - Animator.AnimatorListener listener) { - setStartDelay(ALL, startDelay); - setDuration(ALL, duration); - setInterpolator(ALL, interpolator); - setListener(listener); - } - - /** - * Creates a new {@link AnimatorSet} that will animate the given animators. Callers need to - * manually apply the individual animation properties for each of the animators respectively. - */ - public AnimatorSet createAnimator(List<Animator> animators) { - AnimatorSet anim = new AnimatorSet(); - if (mListener != null) { - anim.addListener(mListener); - } - anim.playTogether(animators); - return anim; - } - - /** - * Applies the specific start delay, duration and interpolator to the given {@param animator} - * for the specified {@param propertyType}. - */ - public <T extends ValueAnimator> T apply(@PropType int propertyType, T animator) { - animator.setStartDelay(getStartDelay(propertyType)); - animator.setDuration(getDuration(propertyType)); - animator.setInterpolator(getInterpolator(propertyType)); - return animator; - } - - /** - * Sets a start delay for a specific property. - */ - public AnimationProps setStartDelay(@PropType int propertyType, int startDelay) { - if (mPropStartDelay == null) { - mPropStartDelay = new SparseLongArray(); - } - mPropStartDelay.append(propertyType, startDelay); - return this; - } - - /** - * Returns the start delay for a specific property. - */ - public long getStartDelay(@PropType int propertyType) { - if (mPropStartDelay != null) { - long startDelay = mPropStartDelay.get(propertyType, -1); - if (startDelay != -1) { - return startDelay; - } - return mPropStartDelay.get(ALL, 0); - } - return 0; - } - - /** - * Sets a duration for a specific property. - */ - public AnimationProps setDuration(@PropType int propertyType, int duration) { - if (mPropDuration == null) { - mPropDuration = new SparseLongArray(); - } - mPropDuration.append(propertyType, duration); - return this; - } - - /** - * Returns the duration for a specific property. - */ - public long getDuration(@PropType int propertyType) { - if (mPropDuration != null) { - long duration = mPropDuration.get(propertyType, -1); - if (duration != -1) { - return duration; - } - return mPropDuration.get(ALL, 0); - } - return 0; - } - - /** - * Sets an interpolator for a specific property. - */ - public AnimationProps setInterpolator(@PropType int propertyType, Interpolator interpolator) { - if (mPropInterpolators == null) { - mPropInterpolators = new SparseArray<>(); - } - mPropInterpolators.append(propertyType, interpolator); - return this; - } - - /** - * Returns the interpolator for a specific property, falling back to the general interpolator - * if there is no specific property interpolator. - */ - public Interpolator getInterpolator(@PropType int propertyType) { - if (mPropInterpolators != null) { - Interpolator interp = mPropInterpolators.get(propertyType); - if (interp != null) { - return interp; - } - return mPropInterpolators.get(ALL, LINEAR_INTERPOLATOR); - } - return LINEAR_INTERPOLATOR; - } - - /** - * Sets an animator listener for this animation. - */ - public AnimationProps setListener(Animator.AnimatorListener listener) { - mListener = listener; - return this; - } - - /** - * Returns the animator listener for this animation. - */ - public Animator.AnimatorListener getListener() { - return mListener; - } - - /** - * Returns whether this animation has any duration. - */ - public boolean isImmediate() { - int count = mPropDuration.size(); - for (int i = 0; i < count; i++) { - if (mPropDuration.valueAt(i) > 0) { - return false; - } - } - return true; - } -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java index 7d159b745254..7dffc2613956 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * 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. @@ -16,158 +16,19 @@ package com.android.systemui.shared.recents.utilities; -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.RectEvaluator; -import android.annotation.FloatRange; -import android.annotation.Nullable; -import android.app.Activity; -import android.content.Context; -import android.content.res.Configuration; -import android.content.res.Resources; import android.graphics.Color; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.Message; -import android.os.Trace; -import android.util.ArraySet; -import android.util.IntProperty; -import android.util.Property; -import android.util.TypedValue; -import android.view.Surface; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.view.ViewRootImpl; -import android.view.ViewStub; - -import java.util.ArrayList; -import java.util.Collections; /* Common code */ public class Utilities { - public static final Property<Drawable, Integer> DRAWABLE_ALPHA = - new IntProperty<Drawable>("drawableAlpha") { - @Override - public void setValue(Drawable object, int alpha) { - object.setAlpha(alpha); - } - - @Override - public Integer get(Drawable object) { - return object.getAlpha(); - } - }; - - public static final Property<Drawable, Rect> DRAWABLE_RECT = - new Property<Drawable, Rect>(Rect.class, "drawableBounds") { - @Override - public void set(Drawable object, Rect bounds) { - object.setBounds(bounds); - } - - @Override - public Rect get(Drawable object) { - return object.getBounds(); - } - }; - - public static final RectFEvaluator RECTF_EVALUATOR = new RectFEvaluator(); - public static final RectEvaluator RECT_EVALUATOR = new RectEvaluator(new Rect()); - - /** - * @return the first parent walking up the view hierarchy that has the given class type. - * - * @param parentClass must be a class derived from {@link View} - */ - public static <T extends View> T findParent(View v, Class<T> parentClass) { - ViewParent parent = v.getParent(); - while (parent != null) { - if (parentClass.isAssignableFrom(parent.getClass())) { - return (T) parent; - } - parent = parent.getParent(); - } - return null; - } - - /** - * Initializes the {@param setOut} with the given object. - */ - public static <T> ArraySet<T> objectToSet(T obj, ArraySet<T> setOut) { - setOut.clear(); - if (obj != null) { - setOut.add(obj); - } - return setOut; - } - /** - * Replaces the contents of {@param setOut} with the contents of the {@param array}. - */ - public static <T> ArraySet<T> arrayToSet(T[] array, ArraySet<T> setOut) { - setOut.clear(); - if (array != null) { - Collections.addAll(setOut, array); - } - return setOut; - } - - /** - * @return the clamped {@param value} between the provided {@param min} and {@param max}. - */ - public static float clamp(float value, float min, float max) { - return Math.max(min, Math.min(max, value)); - } - - /** - * @return the clamped {@param value} between the provided {@param min} and {@param max}. - */ - public static int clamp(int value, int min, int max) { - return Math.max(min, Math.min(max, value)); - } - - /** - * @return the clamped {@param value} between 0 and 1. - */ - public static float clamp01(float value) { - return Math.max(0f, Math.min(1f, value)); - } - - /** - * Scales the {@param value} to be proportionally between the {@param min} and - * {@param max} values. - * - * @param value must be between 0 and 1 - */ - public static float mapRange(@FloatRange(from=0.0,to=1.0) float value, float min, float max) { - return min + (value * (max - min)); - } - - /** - * Scales the {@param value} proportionally from {@param min} and {@param max} to 0 and 1. - * - * @param value must be between {@param min} and {@param max} + * Posts a runnable on a handler at the front of the queue ignoring any sync barriers. */ - public static float unmapRange(float value, float min, float max) { - return (value - min) / (max - min); - } - - /** Scales a rect about its centroid */ - public static void scaleRectAboutCenter(RectF r, float scale) { - if (scale != 1.0f) { - float cx = r.centerX(); - float cy = r.centerY(); - r.offset(-cx, -cy); - r.left *= scale; - r.top *= scale; - r.right *= scale; - r.bottom *= scale; - r.offset(cx, cy); - } + public static void postAtFrontOfQueueAsynchronously(Handler h, Runnable r) { + Message msg = h.obtainMessage().setCallback(r); + h.sendMessageAtFrontOfQueue(msg); } /** Calculates the constrast between two colors, using the algorithm provided by the WCAG v2. */ @@ -179,7 +40,7 @@ public class Utilities { bgG = (bgG < 0.03928f) ? bgG / 12.92f : (float) Math.pow((bgG + 0.055f) / 1.055f, 2.4f); bgB = (bgB < 0.03928f) ? bgB / 12.92f : (float) Math.pow((bgB + 0.055f) / 1.055f, 2.4f); float bgL = 0.2126f * bgR + 0.7152f * bgG + 0.0722f * bgB; - + float fgR = Color.red(fg) / 255f; float fgG = Color.green(fg) / 255f; float fgB = Color.blue(fg) / 255f; @@ -191,147 +52,10 @@ public class Utilities { return Math.abs((fgL + 0.05f) / (bgL + 0.05f)); } - /** Returns the base color overlaid with another overlay color with a specified alpha. */ - public static int getColorWithOverlay(int baseColor, int overlayColor, float overlayAlpha) { - return Color.rgb( - (int) (overlayAlpha * Color.red(baseColor) + - (1f - overlayAlpha) * Color.red(overlayColor)), - (int) (overlayAlpha * Color.green(baseColor) + - (1f - overlayAlpha) * Color.green(overlayColor)), - (int) (overlayAlpha * Color.blue(baseColor) + - (1f - overlayAlpha) * Color.blue(overlayColor))); - } - - /** - * Cancels an animation ensuring that if it has listeners, onCancel and onEnd - * are not called. - */ - public static void cancelAnimationWithoutCallbacks(Animator animator) { - if (animator != null && animator.isStarted()) { - removeAnimationListenersRecursive(animator); - animator.cancel(); - } - } - /** - * Recursively removes all the listeners of all children of this animator - */ - public static void removeAnimationListenersRecursive(Animator animator) { - if (animator instanceof AnimatorSet) { - ArrayList<Animator> animators = ((AnimatorSet) animator).getChildAnimations(); - for (int i = animators.size() - 1; i >= 0; i--) { - removeAnimationListenersRecursive(animators.get(i)); - } - } - animator.removeAllListeners(); - } - - /** - * Sets the given {@link View}'s frame from its current translation. - */ - public static void setViewFrameFromTranslation(View v) { - RectF taskViewRect = new RectF(v.getLeft(), v.getTop(), v.getRight(), v.getBottom()); - taskViewRect.offset(v.getTranslationX(), v.getTranslationY()); - v.setTranslationX(0); - v.setTranslationY(0); - v.setLeftTopRightBottom((int) taskViewRect.left, (int) taskViewRect.top, - (int) taskViewRect.right, (int) taskViewRect.bottom); - } - - /** - * Returns a view stub for the given view id. - */ - public static ViewStub findViewStubById(View v, int stubId) { - return (ViewStub) v.findViewById(stubId); - } - - /** - * Returns a view stub for the given view id. - */ - public static ViewStub findViewStubById(Activity a, int stubId) { - return (ViewStub) a.findViewById(stubId); - } - - /** - * Used for debugging, converts DP to PX. - */ - public static float dpToPx(Resources res, float dp) { - return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, res.getDisplayMetrics()); - } - - /** - * Adds a trace event for debugging. - */ - public static void addTraceEvent(String event) { - Trace.traceBegin(Trace.TRACE_TAG_VIEW, event); - Trace.traceEnd(Trace.TRACE_TAG_VIEW); - } - - /** - * Returns whether this view, or one of its descendants have accessibility focus. - */ - public static boolean isDescendentAccessibilityFocused(View v) { - if (v.isAccessibilityFocused()) { - return true; - } - - if (v instanceof ViewGroup) { - ViewGroup vg = (ViewGroup) v; - int childCount = vg.getChildCount(); - for (int i = 0; i < childCount; i++) { - if (isDescendentAccessibilityFocused(vg.getChildAt(i))) { - return true; - } - } - } - return false; - } - - /** - * Returns the application configuration, which is independent of the activity's current - * configuration in multiwindow. - */ - public static Configuration getAppConfiguration(Context context) { - return context.getApplicationContext().getResources().getConfiguration(); - } - - /** - * @return The next frame name for the specified surface or -1 if the surface is no longer - * valid. - */ - public static long getNextFrameNumber(Surface s) { - return s != null && s.isValid() - ? s.getNextFrameNumber() - : -1; - - } - - /** - * @return The surface for the specified view. - */ - public static @Nullable Surface getSurface(View v) { - ViewRootImpl viewRoot = v.getViewRootImpl(); - if (viewRoot == null) { - return null; - } - return viewRoot.mSurface; - } - - /** - * Returns a lightweight dump of a rect. - */ - public static String dumpRect(Rect r) { - if (r == null) { - return "N:0,0-0,0"; - } - return r.left + "," + r.top + "-" + r.right + "," + r.bottom; - } - - /** - * Posts a runnable on a handler at the front of the queue ignoring any sync barriers. + * @return the clamped {@param value} between the provided {@param min} and {@param max}. */ - public static void postAtFrontOfQueueAsynchronously(Handler h, Runnable r) { - Message msg = h.obtainMessage().setCallback(r); - h.sendMessageAtFrontOfQueue(msg); + public static float clamp(float value, float min, float max) { + return Math.max(min, Math.min(max, value)); } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/AnimateableViewBounds.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/AnimateableViewBounds.java deleted file mode 100644 index 45728c403ac4..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/AnimateableViewBounds.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2014 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.shared.recents.view; - -import android.graphics.Outline; -import android.graphics.Rect; -import android.view.View; -import android.view.ViewDebug; -import android.view.ViewOutlineProvider; - -import com.android.systemui.shared.recents.utilities.Utilities; - -/** - * An outline provider that has a clip and outline that can be animated. - */ -public class AnimateableViewBounds extends ViewOutlineProvider { - - private static final float MIN_ALPHA = 0.1f; - private static final float MAX_ALPHA = 0.8f; - - protected View mSourceView; - protected Rect mClipRect = new Rect(); - protected Rect mClipBounds = new Rect(); - protected Rect mLastClipBounds = new Rect(); - protected int mCornerRadius; - protected float mAlpha = 1f; - - public AnimateableViewBounds(View source, int cornerRadius) { - mSourceView = source; - mCornerRadius = cornerRadius; - } - - /** - * Resets the right and bottom clip for this view. - */ - public void reset() { - mClipRect.set(0, 0, 0, 0); - updateClipBounds(); - } - - @Override - public void getOutline(View view, Outline outline) { - outline.setAlpha(Utilities.mapRange(mAlpha, MIN_ALPHA, MAX_ALPHA)); - if (mCornerRadius > 0) { - outline.setRoundRect(mClipRect.left, mClipRect.top, - mSourceView.getWidth() - mClipRect.right, - mSourceView.getHeight() - mClipRect.bottom, - mCornerRadius); - } else { - outline.setRect(mClipRect.left, mClipRect.top, - mSourceView.getWidth() - mClipRect.right, - mSourceView.getHeight() - mClipRect.bottom); - } - } - - /** - * Sets the view outline alpha. - */ - public void setAlpha(float alpha) { - if (Float.compare(alpha, mAlpha) != 0) { - mAlpha = alpha; - // TODO, If both clip and alpha change in the same frame, only invalidate once - mSourceView.invalidateOutline(); - } - } - - /** - * @return the outline alpha. - */ - public float getAlpha() { - return mAlpha; - } - - /** - * Sets the top clip. - */ - public void setClipTop(int top) { - mClipRect.top = top; - updateClipBounds(); - } - - /** - * @return the top clip. - */ - public int getClipTop() { - return mClipRect.top; - } - - /** - * Sets the bottom clip. - */ - public void setClipBottom(int bottom) { - mClipRect.bottom = bottom; - updateClipBounds(); - } - - /** - * @return the bottom clip. - */ - public int getClipBottom() { - return mClipRect.bottom; - } - - /** - * @return the clip bounds. - */ - public Rect getClipBounds() { - return mClipBounds; - } - - protected void updateClipBounds() { - mClipBounds.set(Math.max(0, mClipRect.left), Math.max(0, mClipRect.top), - mSourceView.getWidth() - Math.max(0, mClipRect.right), - mSourceView.getHeight() - Math.max(0, mClipRect.bottom)); - if (!mLastClipBounds.equals(mClipBounds)) { - mSourceView.setClipBounds(mClipBounds); - // TODO, If both clip and alpha change in the same frame, only invalidate once - mSourceView.invalidateOutline(); - mLastClipBounds.set(mClipBounds); - } - } -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/RecentsTransition.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/RecentsTransition.java index ab890439a381..e253360a5364 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/RecentsTransition.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/RecentsTransition.java @@ -21,13 +21,11 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.GraphicBuffer; +import android.graphics.Picture; import android.os.Bundle; import android.os.Handler; import android.os.IRemoteCallback; import android.os.RemoteException; -import android.view.DisplayListCanvas; -import android.view.RenderNode; -import android.view.ThreadedRenderer; import android.view.View; import java.util.function.Consumer; @@ -108,12 +106,10 @@ public class RecentsTransition { * null if we were unable to allocate a hardware bitmap. */ public static Bitmap createHardwareBitmap(int width, int height, Consumer<Canvas> consumer) { - RenderNode node = RenderNode.create("RecentsTransition", null); - node.setLeftTopRightBottom(0, 0, width, height); - node.setClipToBounds(false); - DisplayListCanvas c = node.start(width, height); - consumer.accept(c); - node.end(c); - return ThreadedRenderer.createHardwareBitmap(node, width, height); + final Picture picture = new Picture(); + final Canvas canvas = picture.beginRecording(width, height); + consumer.accept(canvas); + picture.endRecording(); + return Bitmap.createBitmap(picture); } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java index 63a4cd497bc1..c7910f97675e 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java @@ -30,6 +30,7 @@ import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityManager.RecentTaskInfo; import android.app.ActivityOptions; +import android.app.ActivityTaskManager; import android.app.AppGlobals; import android.app.IAssistDataReceiver; import android.app.WindowConfiguration.ActivityType; @@ -113,7 +114,7 @@ public class ActivityManagerWrapper { // Note: The set of running tasks from the system is ordered by recency try { List<ActivityManager.RunningTaskInfo> tasks = - ActivityManager.getService().getFilteredTasks(1, ignoreActivityType, + ActivityTaskManager.getService().getFilteredTasks(1, ignoreActivityType, WINDOWING_MODE_PINNED /* ignoreWindowingMode */); if (tasks.isEmpty()) { return null; @@ -129,7 +130,7 @@ public class ActivityManagerWrapper { */ public List<RecentTaskInfo> getRecentTasks(int numTasks, int userId) { try { - return ActivityManager.getService().getRecentTasks(numTasks, + return ActivityTaskManager.getService().getRecentTasks(numTasks, RECENT_IGNORE_UNAVAILABLE, userId).getList(); } catch (RemoteException e) { Log.e(TAG, "Failed to get recent tasks", e); @@ -143,7 +144,7 @@ public class ActivityManagerWrapper { public @NonNull ThumbnailData getTaskThumbnail(int taskId, boolean reducedResolution) { ActivityManager.TaskSnapshot snapshot = null; try { - snapshot = ActivityManager.getService().getTaskSnapshot(taskId, reducedResolution); + snapshot = ActivityTaskManager.getService().getTaskSnapshot(taskId, reducedResolution); } catch (RemoteException e) { Log.w(TAG, "Failed to retrieve task snapshot", e); } @@ -200,8 +201,8 @@ public class ActivityManagerWrapper { /** * Starts the recents activity. The caller should manage the thread on which this is called. */ - public void startRecentsActivity(Intent intent, AssistDataReceiver assistDataReceiver, - RecentsAnimationListener animationHandler, Consumer<Boolean> resultCallback, + public void startRecentsActivity(Intent intent, final AssistDataReceiver assistDataReceiver, + final RecentsAnimationListener animationHandler, final Consumer<Boolean> resultCallback, Handler resultCallbackHandler) { try { IAssistDataReceiver receiver = null; @@ -234,7 +235,7 @@ public class ActivityManagerWrapper { } }; } - ActivityManager.getService().startRecentsActivity(intent, receiver, runner); + ActivityTaskManager.getService().startRecentsActivity(intent, receiver, runner); if (resultCallback != null) { resultCallbackHandler.post(new Runnable() { @Override @@ -260,7 +261,7 @@ public class ActivityManagerWrapper { */ public void cancelRecentsAnimation(boolean restoreHomeStackPosition) { try { - ActivityManager.getService().cancelRecentsAnimation(restoreHomeStackPosition); + ActivityTaskManager.getService().cancelRecentsAnimation(restoreHomeStackPosition); } catch (RemoteException e) { Log.e(TAG, "Failed to cancel recents animation", e); } @@ -283,9 +284,9 @@ public class ActivityManagerWrapper { * @param resultCallback The result success callback * @param resultCallbackHandler The handler to receive the result callback */ - public void startActivityFromRecentsAsync(Task.TaskKey taskKey, ActivityOptions options, - int windowingMode, int activityType, Consumer<Boolean> resultCallback, - Handler resultCallbackHandler) { + public void startActivityFromRecentsAsync(final Task.TaskKey taskKey, ActivityOptions options, + int windowingMode, int activityType, final Consumer<Boolean> resultCallback, + final Handler resultCallbackHandler) { if (taskKey.windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { // We show non-visible docked tasks in Recents, but we always want to launch // them in the fullscreen stack. @@ -333,7 +334,7 @@ public class ActivityManagerWrapper { public boolean startActivityFromRecents(int taskId, ActivityOptions options) { try { Bundle optsBundle = options == null ? null : options.toBundle(); - ActivityManager.getService().startActivityFromRecents(taskId, optsBundle); + ActivityTaskManager.getService().startActivityFromRecents(taskId, optsBundle); return true; } catch (Exception e) { return false; @@ -341,6 +342,20 @@ public class ActivityManagerWrapper { } /** + * Moves an already resumed task to the side of the screen to initiate split screen. + */ + public boolean setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode, + Rect initialBounds) { + try { + return ActivityTaskManager.getService().setTaskWindowingModeSplitScreenPrimary(taskId, + createMode, true /* onTop */, false /* animate */, initialBounds, + true /* showRecents */); + } catch (RemoteException e) { + return false; + } + } + + /** * Registers a task stack listener with the system. * This should be called on the main thread. */ @@ -363,7 +378,7 @@ public class ActivityManagerWrapper { /** * Requests that the system close any open system windows (including other SystemUI). */ - public void closeSystemWindows(String reason) { + public void closeSystemWindows(final String reason) { mBackgroundExecutor.submit(new Runnable() { @Override public void run() { @@ -379,12 +394,12 @@ public class ActivityManagerWrapper { /** * Removes a task by id. */ - public void removeTask(int taskId) { + public void removeTask(final int taskId) { mBackgroundExecutor.submit(new Runnable() { @Override public void run() { try { - ActivityManager.getService().removeTask(taskId); + ActivityTaskManager.getService().removeTask(taskId); } catch (RemoteException e) { Log.w(TAG, "Failed to remove task=" + taskId, e); } @@ -393,11 +408,27 @@ public class ActivityManagerWrapper { } /** + * Removes all the recent tasks. + */ + public void removeAllRecentTasks() { + mBackgroundExecutor.submit(new Runnable() { + @Override + public void run() { + try { + ActivityTaskManager.getService().removeAllVisibleRecentTasks(); + } catch (RemoteException e) { + Log.w(TAG, "Failed to remove all tasks", e); + } + } + }); + } + + /** * Cancels the current window transtion to/from Recents for the given task id. */ public void cancelWindowTransition(int taskId) { try { - ActivityManager.getService().cancelTaskWindowTransition(taskId); + ActivityTaskManager.getService().cancelTaskWindowTransition(taskId); } catch (RemoteException e) { Log.w(TAG, "Failed to cancel window transition for task=" + taskId, e); } @@ -408,7 +439,7 @@ public class ActivityManagerWrapper { */ public boolean isScreenPinningActive() { try { - return ActivityManager.getService().getLockTaskModeState() == LOCK_TASK_MODE_PINNED; + return ActivityTaskManager.getService().getLockTaskModeState() == LOCK_TASK_MODE_PINNED; } catch (RemoteException e) { return false; } @@ -427,7 +458,7 @@ public class ActivityManagerWrapper { */ public boolean isLockToAppActive() { try { - return ActivityManager.getService().getLockTaskModeState() != LOCK_TASK_MODE_NONE; + return ActivityTaskManager.getService().getLockTaskModeState() != LOCK_TASK_MODE_NONE; } catch (RemoteException e) { return false; } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java index 712cca67c5d6..7154f5396fbd 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java @@ -16,11 +16,13 @@ package com.android.systemui.shared.system; -import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT; -import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; +import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT; +import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import android.app.ActivityOptions; +import android.content.Context; +import android.os.Handler; /** * Wrapper around internal ActivityOptions creation. @@ -43,4 +45,17 @@ public abstract class ActivityOptionsCompat { RemoteAnimationAdapterCompat remoteAnimationAdapter) { return ActivityOptions.makeRemoteAnimation(remoteAnimationAdapter.getWrapped()); } + + public static ActivityOptions makeCustomAnimation(Context context, int enterResId, + int exitResId, final Runnable callback, final Handler callbackHandler) { + return ActivityOptions.makeCustomAnimation(context, enterResId, exitResId, callbackHandler, + new ActivityOptions.OnAnimationStartedListener() { + @Override + public void onAnimationStarted() { + if (callback != null) { + callbackHandler.post(callback); + } + } + }); + } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/DockedStackListenerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/DockedStackListenerCompat.java new file mode 100644 index 000000000000..bb319e625b44 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/DockedStackListenerCompat.java @@ -0,0 +1,63 @@ +/* + * 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.systemui.shared.system; + +import android.view.IDockedStackListener; + +/** + * An interface to track docked stack changes. + */ +public class DockedStackListenerCompat { + + IDockedStackListener.Stub mListener = new IDockedStackListener.Stub() { + @Override + public void onDividerVisibilityChanged(boolean visible) {} + + @Override + public void onDockedStackExistsChanged(boolean exists) { + DockedStackListenerCompat.this.onDockedStackExistsChanged(exists); + } + + @Override + public void onDockedStackMinimizedChanged(boolean minimized, long animDuration, + boolean isHomeStackResizable) { + DockedStackListenerCompat.this.onDockedStackMinimizedChanged(minimized, animDuration, + isHomeStackResizable); + } + + @Override + public void onAdjustedForImeChanged(boolean adjustedForIme, long animDuration) {} + + @Override + public void onDockSideChanged(final int newDockSide) { + DockedStackListenerCompat.this.onDockSideChanged(newDockSide); + } + }; + + public void onDockedStackExistsChanged(boolean exists) { + // To be overridden + } + + public void onDockedStackMinimizedChanged(boolean minimized, long animDuration, + boolean isHomeStackResizable) { + // To be overridden + } + + public void onDockSideChanged(final int newDockSide) { + // To be overridden + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/GraphicBufferCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/GraphicBufferCompat.java deleted file mode 100644 index 66b8fed1a48f..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/GraphicBufferCompat.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2017 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.shared.system; - -import android.graphics.Bitmap; -import android.graphics.GraphicBuffer; -import android.os.Parcel; -import android.os.Parcelable; - -/** - * Wraps the internal graphic buffer. - */ -public class GraphicBufferCompat implements Parcelable { - - private GraphicBuffer mBuffer; - - public GraphicBufferCompat(GraphicBuffer buffer) { - mBuffer = buffer; - } - - public GraphicBufferCompat(Parcel in) { - mBuffer = GraphicBuffer.CREATOR.createFromParcel(in); - } - - public Bitmap toBitmap() { - return mBuffer != null - ? Bitmap.createHardwareBitmap(mBuffer) - : null; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - mBuffer.writeToParcel(dest, flags); - } - - public static final Parcelable.Creator<GraphicBufferCompat> CREATOR - = new Parcelable.Creator<GraphicBufferCompat>() { - public GraphicBufferCompat createFromParcel(Parcel in) { - return new GraphicBufferCompat(in); - } - - public GraphicBufferCompat[] newArray(int size) { - return new GraphicBufferCompat[size]; - } - }; - - @Override - public int describeContents() { - return 0; - } -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputConsumerController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputConsumerController.java index 38b8ae8418af..0cde9daa81b3 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputConsumerController.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputConsumerController.java @@ -16,6 +16,7 @@ package com.android.systemui.shared.system; +import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.INPUT_CONSUMER_PIP; import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION; @@ -67,7 +68,7 @@ public class InputConsumerController { } @Override - public void onInputEvent(InputEvent event, int displayId) { + public void onInputEvent(InputEvent event) { boolean handled = true; try { if (mListener != null && event instanceof MotionEvent) { @@ -146,8 +147,9 @@ public class InputConsumerController { if (mInputEventReceiver == null) { final InputChannel inputChannel = new InputChannel(); try { - mWindowManager.destroyInputConsumer(mName); - mWindowManager.createInputConsumer(mToken, mName, inputChannel); + // TODO(b/113087003): Support Picture-in-picture in multi-display. + mWindowManager.destroyInputConsumer(mName, DEFAULT_DISPLAY); + mWindowManager.createInputConsumer(mToken, mName, DEFAULT_DISPLAY, inputChannel); } catch (RemoteException e) { Log.e(TAG, "Failed to create input consumer", e); } @@ -164,7 +166,8 @@ public class InputConsumerController { public void unregisterInputConsumer() { if (mInputEventReceiver != null) { try { - mWindowManager.destroyInputConsumer(mName); + // TODO(b/113087003): Support Picture-in-picture in multi-display. + mWindowManager.destroyInputConsumer(mName, DEFAULT_DISPLAY); } catch (RemoteException e) { Log.e(TAG, "Failed to destroy input consumer", e); } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/GraphicBufferCompat.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/system/KeyguardManagerCompat.java index f9450adcdf30..c42e7e3fd216 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/GraphicBufferCompat.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/KeyguardManagerCompat.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * 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. @@ -11,9 +11,22 @@ * 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. + * limitations under the License */ package com.android.systemui.shared.system; -parcelable GraphicBufferCompat;
\ No newline at end of file +import android.app.KeyguardManager; +import android.content.Context; + +public class KeyguardManagerCompat { + private final KeyguardManager mKeyguardManager; + + public KeyguardManagerCompat(Context context) { + mKeyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); + } + + public boolean isDeviceLocked(int userId) { + return mKeyguardManager.isDeviceLocked(userId); + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentTaskInfoCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentTaskInfoCompat.java new file mode 100644 index 000000000000..a5299038d3a9 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentTaskInfoCompat.java @@ -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.systemui.shared.system; + +import android.app.ActivityManager; +import android.content.ComponentName; + +public class RecentTaskInfoCompat { + + private ActivityManager.RecentTaskInfo mInfo; + + public RecentTaskInfoCompat(ActivityManager.RecentTaskInfo info) { + mInfo = info; + } + + public int getUserId() { + return mInfo.userId; + } + + public boolean supportsSplitScreenMultiWindow() { + return mInfo.supportsSplitScreenMultiWindow; + } + + public ComponentName getTopActivity() { + return mInfo.topActivity; + } + + public ActivityManager.TaskDescription getTaskDescription() { + return mInfo.taskDescription; + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java index 625b1de74290..61be076ac48a 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java @@ -41,11 +41,11 @@ public class RemoteAnimationAdapterCompat { } private static IRemoteAnimationRunner.Stub wrapRemoteAnimationRunner( - RemoteAnimationRunnerCompat remoteAnimationAdapter) { + final RemoteAnimationRunnerCompat remoteAnimationAdapter) { return new IRemoteAnimationRunner.Stub() { @Override public void onAnimationStart(RemoteAnimationTarget[] apps, - IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException { + final IRemoteAnimationFinishedCallback finishedCallback) { final RemoteAnimationTargetCompat[] appsCompat = RemoteAnimationTargetCompat.wrap(apps); final Runnable animationFinishedCallback = new Runnable() { @@ -63,7 +63,7 @@ public class RemoteAnimationAdapterCompat { } @Override - public void onAnimationCancelled() throws RemoteException { + public void onAnimationCancelled() { remoteAnimationAdapter.onAnimationCancelled(); } }; diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RotationWatcher.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RotationWatcher.java index 5a28a5e28d91..7c8c23eba73b 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RotationWatcher.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RotationWatcher.java @@ -48,7 +48,7 @@ public abstract class RotationWatcher { if (!mIsWatching) { try { WindowManagerGlobal.getWindowManagerService().watchRotation(mWatcher, - mContext.getDisplay().getDisplayId()); + mContext.getDisplayId()); mIsWatching = true; } catch (RemoteException e) { Log.w(TAG, "Failed to set rotation watcher", e); diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/StatsLogCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/StatsLogCompat.java new file mode 100644 index 000000000000..5bc1f2511411 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/StatsLogCompat.java @@ -0,0 +1,35 @@ +/* + * 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.systemui.shared.system; + +import android.util.StatsLog; + +/** + * Wrapper class to make StatsLog hidden API accessible. + */ +public class StatsLogCompat { + + /** + * StatsLog.write(StatsLog.LAUNCHER_EVENT, int action, int src_state, int dst_state, + * byte[] extension, boolean is_swipe_up_enabled); + */ + public static void write(int action, int srcState, int dstState, byte [] extension, + boolean swipeUpEnabled) { + StatsLog.write(19, action, srcState, dstState, extension, + swipeUpEnabled); + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplier.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplier.java index 217e0aaac709..dc4eb3b71faa 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplier.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplier.java @@ -21,6 +21,7 @@ import android.graphics.Rect; import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; +import android.view.ThreadedRenderer; import android.view.View; import android.view.ViewRootImpl; @@ -47,11 +48,13 @@ public class SyncRtSurfaceTransactionApplier { * @param params The surface parameters to apply. DO NOT MODIFY the list after passing into * this method to avoid synchronization issues. */ - public void scheduleApply(SurfaceParams... params) { + public void scheduleApply(final SurfaceParams... params) { if (mTargetViewRootImpl == null) { return; } - mTargetViewRootImpl.registerRtFrameCallback(frame -> { + mTargetViewRootImpl.registerRtFrameCallback(new ThreadedRenderer.FrameDrawingCallback() { + @Override + public void onFrameDraw(long frame) { if (mTargetSurface == null || !mTargetSurface.isValid()) { return; } @@ -64,6 +67,7 @@ public class SyncRtSurfaceTransactionApplier { } t.setEarlyWakeup(); t.apply(); + } }); // Make sure a frame gets scheduled. diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskDescriptionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskDescriptionCompat.java new file mode 100644 index 000000000000..eaf8d9b57398 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskDescriptionCompat.java @@ -0,0 +1,40 @@ +/* + * 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.systemui.shared.system; + +import android.app.ActivityManager; + +public class TaskDescriptionCompat { + + private ActivityManager.TaskDescription mTaskDescription; + + public TaskDescriptionCompat(ActivityManager.TaskDescription td) { + mTaskDescription = td; + } + + public int getPrimaryColor() { + return mTaskDescription != null + ? mTaskDescription.getPrimaryColor() + : 0; + } + + public int getBackgroundColor() { + return mTaskDescription != null + ? mTaskDescription.getBackgroundColor() + : 0; + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java index 5e293c61c35a..628b3c6e21dc 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java @@ -16,6 +16,7 @@ package com.android.systemui.shared.system; +import android.app.ActivityTaskManager; import android.app.ActivityManager.TaskSnapshot; import android.app.IActivityManager; import android.app.TaskStackListener; @@ -57,7 +58,7 @@ public class TaskStackChangeListeners extends TaskStackListener { if (!mRegistered) { // Register mTaskStackListener to IActivityManager only once if needed. try { - am.registerTaskStackListener(this); + ActivityTaskManager.getService().registerTaskStackListener(this); mRegistered = true; } catch (Exception e) { Log.w(TAG, "Failed to call registerTaskStackListener", e); diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java index 67afee076efe..70258c20538d 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java @@ -83,11 +83,6 @@ public class TransactionCompat { return this; } - public TransactionCompat setFinalCrop(SurfaceControlCompat surfaceControl, Rect crop) { - mTransaction.setFinalCrop(surfaceControl.mSurfaceControl, crop); - return this; - } - public TransactionCompat deferTransactionUntil(SurfaceControlCompat surfaceControl, IBinder handle, long frameNumber) { mTransaction.deferTransactionUntil(surfaceControl.mSurfaceControl, handle, frameNumber); diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowCallbacksCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowCallbacksCompat.java index b2b140e4b0a9..de2a3e44841e 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowCallbacksCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowCallbacksCompat.java @@ -17,7 +17,7 @@ package com.android.systemui.shared.system; import android.graphics.Canvas; import android.graphics.Rect; -import android.view.DisplayListCanvas; +import android.graphics.RecordingCanvas; import android.view.View; import android.view.ViewRootImpl; import android.view.WindowCallbacks; @@ -55,7 +55,7 @@ public class WindowCallbacksCompat { } @Override - public void onPostDraw(DisplayListCanvas canvas) { + public void onPostDraw(RecordingCanvas canvas) { WindowCallbacksCompat.this.onPostDraw(canvas); } }; diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java index ed2f8310eff4..3191d14c5a83 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java @@ -151,6 +151,25 @@ public class WindowManagerWrapper { } } + public void setPipVisibility(final boolean visible) { + try { + WindowManagerGlobal.getWindowManagerService().setPipVisibility(visible); + } catch (RemoteException e) { + Log.e(TAG, "Unable to reach window manager", e); + } + } + + /** + * @return whether there is a soft nav bar. + */ + public boolean hasSoftNavigationBar() { + try { + return WindowManagerGlobal.getWindowManagerService().hasNavigationBar(); + } catch (RemoteException e) { + return false; + } + } + /** * @return The side of the screen where navigation bar is positioned. * @see #NAV_BAR_POS_RIGHT @@ -166,4 +185,16 @@ public class WindowManagerWrapper { } return NAV_BAR_POS_INVALID; } + + /** + * Registers a docked stack listener with the system. + */ + public void registerDockedStackListener(DockedStackListenerCompat listener) { + try { + WindowManagerGlobal.getWindowManagerService().registerDockedStackListener( + listener.mListener); + } catch (RemoteException e) { + Log.w(TAG, "Failed to register docked stack listener"); + } + } } |