diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml index b5bd354567a..103594ad370 100644 --- a/AndroidManifest-common.xml +++ b/AndroidManifest-common.xml @@ -60,6 +60,7 @@ + diff --git a/privapp_whitelist_com.android.launcher3-ext.xml b/privapp_whitelist_com.android.launcher3-ext.xml index 99fad541464..9b957898cea 100644 --- a/privapp_whitelist_com.android.launcher3-ext.xml +++ b/privapp_whitelist_com.android.launcher3-ext.xml @@ -26,5 +26,6 @@ + diff --git a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandler.java b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandler.java index 1d00e533f5c..d72bc907526 100644 --- a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandler.java +++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandler.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2024 Neoteric OS * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,23 +17,42 @@ package com.android.quickstep.inputconsumers; +import android.app.contextualsearch.ContextualSearchManager; import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.os.VibrationEffect; +import android.view.ViewConfiguration; import androidx.annotation.Nullable; -import com.android.launcher3.R; -import com.android.launcher3.util.ResourceBasedOverride; +import com.android.launcher3.util.VibratorWrapper; import com.android.quickstep.NavHandle; +import com.android.quickstep.util.AssistUtils; /** * Class for extending nav handle long press behavior */ -public class NavHandleLongPressHandler implements ResourceBasedOverride { +public class NavHandleLongPressHandler { + + private Context mContext; + private AssistUtils mAssistUtils; + + private static final VibrationEffect EFFECT_HEAVY_CLICK = + VibrationEffect.createPredefined(VibrationEffect.EFFECT_HEAVY_CLICK); + private static final VibrationEffect EFFECT_TICK = + VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK); + + private static final Handler mHandler = new Handler(Looper.getMainLooper()); + + public NavHandleLongPressHandler(Context context) { + mContext = context; + mAssistUtils = new AssistUtils(mContext); + } /** Creates NavHandleLongPressHandler as specified by overrides */ public static NavHandleLongPressHandler newInstance(Context context) { - return Overrides.getObject(NavHandleLongPressHandler.class, context, - R.string.nav_handle_long_press_handler_class); + return new NavHandleLongPressHandler(context); } /** @@ -47,7 +67,23 @@ public static NavHandleLongPressHandler newInstance(Context context) { * @param navHandle to handle this long press */ public @Nullable Runnable getLongPressRunnable(NavHandle navHandle) { - return null; + if (mAssistUtils.canDoContextualSearch()) { + VibratorWrapper.INSTANCE.get(mContext).vibrate(EFFECT_TICK); + navHandle.animateNavBarLongPress(true, true, 200L); + return new Runnable() { + @Override + public final void run() { + mHandler.postDelayed(() -> { + if (mAssistUtils.invokeContextualSearch( + ContextualSearchManager.ENTRYPOINT_LONG_PRESS_NAV_HANDLE)) { + VibratorWrapper.INSTANCE.get(mContext).vibrate(EFFECT_HEAVY_CLICK); + } + }, ViewConfiguration.getLongPressTimeout()); + } + }; + } else { + return null; + } } /** @@ -64,5 +100,7 @@ public void onTouchStarted(NavHandle navHandle) {} * @param navHandle to handle the animation for this touch * @param reason why the touch ended */ - public void onTouchFinished(NavHandle navHandle, String reason) {} + public void onTouchFinished(NavHandle navHandle, String reason) { + navHandle.animateNavBarLongPress(false, true, 200L); + } } diff --git a/quickstep/src/com/android/quickstep/util/AssistUtils.java b/quickstep/src/com/android/quickstep/util/AssistUtils.java index 11b6ea7d213..8a39000aeab 100644 --- a/quickstep/src/com/android/quickstep/util/AssistUtils.java +++ b/quickstep/src/com/android/quickstep/util/AssistUtils.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2024 Neoteric OS * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,24 +16,91 @@ */ package com.android.quickstep.util; +import android.app.contextualsearch.ContextualSearchManager; import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.Log; -import com.android.launcher3.R; -import com.android.launcher3.util.ResourceBasedOverride; +import com.android.launcher3.util.IntArray; +import com.android.launcher3.util.SafeCloseable; +import com.android.launcher3.util.SettingsCache; +import com.android.launcher3.util.SimpleBroadcastReceiver; +import com.android.quickstep.SystemUiProxy; + +import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS; + +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; + +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED; /** Utilities to work with Assistant functionality. */ -public class AssistUtils implements ResourceBasedOverride { +public class AssistUtils implements SettingsCache.OnChangeListener, SafeCloseable { + + private final String TAG = "AssistUtils"; + private boolean DEBUG = false; + + private Context mContext; + private ContextualSearchManager mContextualSearchManager; + private String mContextualSearchPkg; + private int mContextualSearchDefValue; + private Intent mCtsPkgIntent; + + private final SimpleBroadcastReceiver mContextualSearchPkgReceiver = + new SimpleBroadcastReceiver(this::onPkgStateChanged); + + private final long KEYGUARD_SHOWING_SYSUI_FLAGS = SYSUI_STATE_BOUNCER_SHOWING | + SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED; + private final long SHADE_EXPANDED_SYSUI_FLAGS = SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED | + SYSUI_STATE_QUICK_SETTINGS_EXPANDED; - public AssistUtils() {} + private final int mCtsPkgQueryFlags = PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE | PackageManager.MATCH_ALL; + + public AssistUtils(Context context) { + mContext = context; + mContextualSearchManager = (ContextualSearchManager) mContext.getSystemService(Context.CONTEXTUAL_SEARCH_SERVICE); + mContextualSearchPkg = mContext.getResources() + .getString(com.android.internal.R.string.config_defaultContextualSearchPackageName); + mContextualSearchDefValue = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_searchAllEntrypointsEnabledDefault) ? 1 : 0; + mCtsPkgIntent = new Intent(ContextualSearchManager.ACTION_LAUNCH_CONTEXTUAL_SEARCH) + .setPackage(mContextualSearchPkg); + + // Register package actions + UI_HELPER_EXECUTOR.execute(() -> { + mContextualSearchPkgReceiver.registerPkgActions(mContext, mContextualSearchPkg, + Intent.ACTION_PACKAGE_ADDED, Intent.ACTION_PACKAGE_CHANGED, + Intent.ACTION_PACKAGE_REMOVED); + }); + + // Register settings cache listener + Uri contextualSearchUri = Settings.Secure.getUriFor(Settings.Secure.SEARCH_ALL_ENTRYPOINTS_ENABLED); + SettingsCache.INSTANCE.get(mContext).register(contextualSearchUri, this); + } /** Creates AssistUtils as specified by overrides */ public static AssistUtils newInstance(Context context) { - return Overrides.getObject(AssistUtils.class, context, R.string.assist_utils_class); + return new AssistUtils(context); } /** @return Array of AssistUtils.INVOCATION_TYPE_* that we want to handle instead of SysUI. */ public int[] getSysUiAssistOverrideInvocationTypes() { - return new int[0]; + if (mContextualSearchManager == null || !isContextualSearchIntentAvailable() || + Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.SEARCH_ALL_ENTRYPOINTS_ENABLED, mContextualSearchDefValue) == 0) { + return new int[0]; + } + IntArray invocationTypes = new IntArray(); + invocationTypes.add(INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS); + return invocationTypes.toArray(); } /** @@ -40,6 +108,70 @@ public int[] getSysUiAssistOverrideInvocationTypes() { * request should be ignored. {@code false} means the caller should start assist another way. */ public boolean tryStartAssistOverride(int invocationType) { - return false; + return invocationType == INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS && + invokeContextualSearch(ContextualSearchManager.ENTRYPOINT_LONG_PRESS_HOME); + } + + public boolean invokeContextualSearch(int invocationType) { + if (!canDoContextualSearch()) { + return false; + } + if (DEBUG) Log.d(TAG, "invokeContextualSearch: Contextual Search should start now"); + mContextualSearchManager.startContextualSearch(invocationType); + return true; + } + + public boolean canDoContextualSearch() { + if (Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.SEARCH_ALL_ENTRYPOINTS_ENABLED, mContextualSearchDefValue) == 0) { + if (DEBUG) Log.d(TAG, "Contextual Search invocation failed: CTS setting disabled"); + return false; + } + + if (mContextualSearchManager == null) { + if (DEBUG) Log.d(TAG, "Contextual Search invocation failed: no ContextualSearchManager"); + return false; + } + + boolean isNotificationShadeShowing = (SystemUiProxy.INSTANCE.get(mContext) + .getLastSystemUiStateFlags() & SHADE_EXPANDED_SYSUI_FLAGS) != 0; + if (isNotificationShadeShowing) { + if (DEBUG) Log.d(TAG, "Contextual Search invocation failed: notification shade"); + return false; + } + + boolean isKeyguardShowing = (SystemUiProxy.INSTANCE.get(mContext) + .getLastSystemUiStateFlags() & KEYGUARD_SHOWING_SYSUI_FLAGS) != 0; + if (isKeyguardShowing) { + if (DEBUG) Log.d(TAG, "Contextual Search invocation failed: keyguard"); + return false; + } + + if (!isContextualSearchIntentAvailable()) { + if (DEBUG) Log.d(TAG, "Contextual Search invocation failed: Contextual Search intent not found"); + return false; + } + + return true; + } + + public boolean isContextualSearchIntentAvailable() { + return !mContext.getPackageManager().queryIntentActivities(mCtsPkgIntent, mCtsPkgQueryFlags).isEmpty(); + } + + private void onPkgStateChanged(Intent intent) { + SystemUiProxy.INSTANCE.get(mContext).setAssistantOverridesRequested(getSysUiAssistOverrideInvocationTypes()); + } + + @Override + public void onSettingsChanged(boolean isEnabled) { + SystemUiProxy.INSTANCE.get(mContext).setAssistantOverridesRequested(getSysUiAssistOverrideInvocationTypes()); + } + + @Override + public void close() { + mContextualSearchPkgReceiver.unregisterReceiverSafely(mContext); + SettingsCache.INSTANCE.get(mContext) + .unregister(Settings.Secure.getUriFor(Settings.Secure.SEARCH_ALL_ENTRYPOINTS_ENABLED), this); } } diff --git a/res/values/cr_strings.xml b/res/values/cr_strings.xml index 651ca599def..78e1c94a47f 100644 --- a/res/values/cr_strings.xml +++ b/res/values/cr_strings.xml @@ -423,4 +423,8 @@ Force themed icons Generate monochromatic icons, if not supported by the app (requires re-toggling of themed icons) + + + Circle to Search + Touch and hold the Home button or the navigation handle to search using the content on your screen. diff --git a/res/xml/launcher_misc_preferences.xml b/res/xml/launcher_misc_preferences.xml index 53cd084defa..3ce4c19676b 100644 --- a/res/xml/launcher_misc_preferences.xml +++ b/res/xml/launcher_misc_preferences.xml @@ -48,6 +48,13 @@ android:min="0" settings:units="px" android:defaultValue="23" /> + + app.setNeedsRestart()); break; + case CTS_KEY: + mCtsEnabled = LauncherPrefs.getPrefs(mContext).getBoolean(CTS_KEY, mContextualSearchDefValue); + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.SEARCH_ALL_ENTRYPOINTS_ENABLED, mCtsEnabled ? 1 : 0); + break; default: break; } @@ -189,6 +205,8 @@ public static class MiscSettingsFragment extends PreferenceFragmentCompat implem private String mHighLightKey; private boolean mPreferenceHighlighted = false; + private SwitchPreferenceCompat mCtsPref; + @Override public void onCreate(@Nullable Bundle savedInstanceState) { if (BuildConfig.IS_DEBUG_DEVICE) { @@ -223,6 +241,15 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { if (getActivity() != null && !TextUtils.isEmpty(getPreferenceScreen().getTitle())) { getActivity().setTitle(getPreferenceScreen().getTitle()); } + + mCtsPref = (SwitchPreferenceCompat) findPreference(CTS_KEY); + mCtsEnabled = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.SEARCH_ALL_ENTRYPOINTS_ENABLED, mContextualSearchDefValue ? 1 : 0) == 1; + if (!AssistUtils.newInstance(mContext).isContextualSearchIntentAvailable()) { + getPreferenceScreen().removePreference(mCtsPref); + } else { + mCtsPref.setChecked(mCtsEnabled); + } } @Override