1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.quickstep.util; 17 18 import static android.app.contextualsearch.ContextualSearchManager.ACTION_LAUNCH_CONTEXTUAL_SEARCH; 19 import static android.app.contextualsearch.ContextualSearchManager.ENTRYPOINT_SYSTEM_ACTION; 20 import static android.app.contextualsearch.ContextualSearchManager.FEATURE_CONTEXTUAL_SEARCH; 21 import static android.view.Display.DEFAULT_DISPLAY; 22 23 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_OMNI_SUCCESSFUL_SYSTEM_ACTION; 24 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; 25 import static com.android.quickstep.util.SystemActionConstants.SYSTEM_ACTION_ID_SEARCH_SCREEN; 26 27 import android.app.PendingIntent; 28 import android.app.RemoteAction; 29 import android.content.Context; 30 import android.content.IIntentReceiver; 31 import android.content.IIntentSender; 32 import android.content.Intent; 33 import android.content.pm.PackageManager; 34 import android.graphics.drawable.Icon; 35 import android.net.Uri; 36 import android.os.Bundle; 37 import android.os.IBinder; 38 import android.os.RemoteException; 39 import android.provider.Settings; 40 import android.util.Log; 41 import android.view.accessibility.AccessibilityManager; 42 43 import androidx.annotation.CallSuper; 44 import androidx.annotation.VisibleForTesting; 45 46 import com.android.launcher3.R; 47 import com.android.launcher3.dagger.ApplicationContext; 48 import com.android.launcher3.dagger.LauncherAppComponent; 49 import com.android.launcher3.dagger.LauncherAppSingleton; 50 import com.android.launcher3.logging.StatsLogManager; 51 import com.android.launcher3.util.DaggerSingletonObject; 52 import com.android.launcher3.util.DaggerSingletonTracker; 53 import com.android.launcher3.util.EventLogArray; 54 import com.android.launcher3.util.SettingsCache; 55 import com.android.launcher3.util.SimpleBroadcastReceiver; 56 import com.android.quickstep.DeviceConfigWrapper; 57 import com.android.quickstep.SystemUiProxy; 58 import com.android.quickstep.TopTaskTracker; 59 60 import java.io.PrintWriter; 61 import java.util.Optional; 62 63 import javax.inject.Inject; 64 65 /** Long-lived class to manage Contextual Search states like the user setting and availability. */ 66 @LauncherAppSingleton 67 public class ContextualSearchStateManager { 68 69 public static final DaggerSingletonObject<ContextualSearchStateManager> INSTANCE = 70 new DaggerSingletonObject<>(LauncherAppComponent::getContextualSearchStateManager); 71 72 private static final String TAG = "ContextualSearchStMgr"; 73 private static final int MAX_DEBUG_EVENT_SIZE = 20; 74 private static final Uri SEARCH_ALL_ENTRYPOINTS_ENABLED_URI = 75 Settings.Secure.getUriFor(Settings.Secure.SEARCH_ALL_ENTRYPOINTS_ENABLED); 76 77 private final Runnable mSysUiStateChangeListener = this::updateOverridesToSysUi; 78 private final SimpleBroadcastReceiver mContextualSearchPackageReceiver; 79 protected final EventLogArray mEventLogArray = new EventLogArray(TAG, MAX_DEBUG_EVENT_SIZE); 80 81 // Cached value whether the ContextualSearch intent filter matched any enabled components. 82 private boolean mIsContextualSearchIntentAvailable; 83 private boolean mIsContextualSearchSettingEnabled; 84 85 protected final Context mContext; 86 protected final String mContextualSearchPackage; 87 protected final SystemUiProxy mSystemUiProxy; 88 protected final TopTaskTracker mTopTaskTracker; 89 90 @Inject ContextualSearchStateManager( @pplicationContext Context context, SettingsCache settingsCache, SystemUiProxy systemUiProxy, TopTaskTracker topTaskTracker, DaggerSingletonTracker lifeCycle)91 public ContextualSearchStateManager( 92 @ApplicationContext Context context, 93 SettingsCache settingsCache, 94 SystemUiProxy systemUiProxy, 95 TopTaskTracker topTaskTracker, 96 DaggerSingletonTracker lifeCycle) { 97 mContext = context; 98 mContextualSearchPackageReceiver = 99 new SimpleBroadcastReceiver(context, UI_HELPER_EXECUTOR, 100 (unused) -> requestUpdateProperties()); 101 mContextualSearchPackage = mContext.getResources().getString( 102 com.android.internal.R.string.config_defaultContextualSearchPackageName); 103 mSystemUiProxy = systemUiProxy; 104 mTopTaskTracker = topTaskTracker; 105 106 if (areAllContextualSearchFlagsDisabled() 107 || !context.getPackageManager().hasSystemFeature(FEATURE_CONTEXTUAL_SEARCH)) { 108 // If we had previously registered a SystemAction which is no longer valid, we need to 109 // unregister it here. 110 unregisterSearchScreenSystemAction(); 111 // Don't listen for stuff we aren't gonna use. 112 return; 113 } 114 115 requestUpdateProperties(); 116 registerSearchScreenSystemAction(); 117 mContextualSearchPackageReceiver.registerPkgActions( 118 mContextualSearchPackage, Intent.ACTION_PACKAGE_ADDED, 119 Intent.ACTION_PACKAGE_CHANGED, Intent.ACTION_PACKAGE_REMOVED); 120 121 SettingsCache.OnChangeListener settingChangedListener = 122 isEnabled -> mIsContextualSearchSettingEnabled = isEnabled; 123 settingsCache.register(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI, settingChangedListener); 124 mIsContextualSearchSettingEnabled = 125 settingsCache.getValue(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI); 126 127 systemUiProxy.addOnStateChangeListener(mSysUiStateChangeListener); 128 129 lifeCycle.addCloseable(() -> { 130 mContextualSearchPackageReceiver.unregisterReceiverSafely(); 131 unregisterSearchScreenSystemAction(); 132 settingsCache.unregister(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI, settingChangedListener); 133 systemUiProxy.removeOnStateChangeListener(mSysUiStateChangeListener); 134 }); 135 } 136 137 /** Return {@code true} if the Settings toggle is enabled. */ isContextualSearchSettingEnabled()138 public final boolean isContextualSearchSettingEnabled() { 139 return mIsContextualSearchSettingEnabled; 140 } 141 142 /** Whether search supports showing on the lockscreen. */ supportsShowWhenLocked()143 protected boolean supportsShowWhenLocked() { 144 return false; 145 } 146 147 /** Whether ContextualSearchService invocation path is available. */ 148 @VisibleForTesting isContextualSearchIntentAvailable()149 protected final boolean isContextualSearchIntentAvailable() { 150 return mIsContextualSearchIntentAvailable; 151 } 152 153 /** Get the Launcher overridden long press nav handle duration to trigger Assistant. */ getLPNHDurationMillis()154 public Optional<Long> getLPNHDurationMillis() { 155 return Optional.empty(); 156 } 157 158 /** 159 * Get the Launcher overridden long press nav handle touch slop multiplier to trigger Assistant. 160 */ getLPNHCustomSlopMultiplier()161 public Optional<Float> getLPNHCustomSlopMultiplier() { 162 return Optional.empty(); 163 } 164 165 /** Get the Launcher overridden long press home duration to trigger Assistant. */ getLPHDurationMillis()166 public Optional<Long> getLPHDurationMillis() { 167 return Optional.empty(); 168 } 169 170 /** Get the Launcher overridden long press home touch slop multiplier to trigger Assistant. */ getLPHCustomSlopMultiplier()171 public Optional<Float> getLPHCustomSlopMultiplier() { 172 return Optional.empty(); 173 } 174 175 /** Get the long press duration data source. */ getDurationDataSource()176 public int getDurationDataSource() { 177 return 0; 178 } 179 180 /** Get the long press touch slop multiplier data source. */ getSlopDataSource()181 public int getSlopDataSource() { 182 return 0; 183 } 184 185 /** 186 * Get the User group based on the behavior to trigger Assistant. 187 */ getLPUserGroup()188 public Optional<Integer> getLPUserGroup() { 189 return Optional.empty(); 190 } 191 192 /** Get the haptic bit overridden by AGSA. */ getShouldPlayHapticOverride()193 public Optional<Boolean> getShouldPlayHapticOverride() { 194 return Optional.empty(); 195 } 196 isInvocationAllowedOnKeyguard()197 protected boolean isInvocationAllowedOnKeyguard() { 198 return false; 199 } 200 isInvocationAllowedInSplitscreen()201 protected boolean isInvocationAllowedInSplitscreen() { 202 return true; 203 } 204 205 @CallSuper areAllContextualSearchFlagsDisabled()206 protected boolean areAllContextualSearchFlagsDisabled() { 207 return !DeviceConfigWrapper.get().getEnableLongPressNavHandle(); 208 } 209 210 @CallSuper requestUpdateProperties()211 protected void requestUpdateProperties() { 212 UI_HELPER_EXECUTOR.execute(() -> { 213 // Check that Contextual Search intent filters are enabled. 214 Intent csIntent = new Intent(ACTION_LAUNCH_CONTEXTUAL_SEARCH).setPackage( 215 mContextualSearchPackage); 216 mIsContextualSearchIntentAvailable = 217 !mContext.getPackageManager().queryIntentActivities(csIntent, 218 PackageManager.MATCH_DIRECT_BOOT_AWARE 219 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE).isEmpty(); 220 221 addEventLog("Updated isContextualSearchIntentAvailable", 222 mIsContextualSearchIntentAvailable); 223 }); 224 } 225 updateOverridesToSysUi()226 protected final void updateOverridesToSysUi() { 227 // LPH commit haptic is always enabled 228 mSystemUiProxy.setOverrideHomeButtonLongPress( 229 getLPHDurationMillis().orElse(0L), getLPHCustomSlopMultiplier().orElse(0f), true); 230 Log.i(TAG, "Sent LPH override to sysui: " + getLPHDurationMillis().orElse(0L) + ";" 231 + getLPHCustomSlopMultiplier().orElse(0f)); 232 } 233 registerSearchScreenSystemAction()234 private void registerSearchScreenSystemAction() { 235 PendingIntent searchScreenPendingIntent = new PendingIntent(new IIntentSender.Stub() { 236 @Override 237 public void send(int i, Intent intent, String s, IBinder iBinder, 238 IIntentReceiver iIntentReceiver, String s1, Bundle bundle) 239 throws RemoteException { 240 // Delayed slightly to minimize chance of capturing the System Actions dialog. 241 UI_HELPER_EXECUTOR.getHandler().postDelayed( 242 () -> { 243 boolean contextualSearchInvoked = 244 new ContextualSearchInvoker(mContext).show( 245 ENTRYPOINT_SYSTEM_ACTION); 246 if (contextualSearchInvoked) { 247 String runningPackage = mTopTaskTracker.getCachedTopTask( 248 /* filterOnlyVisibleRecents */ true, 249 DEFAULT_DISPLAY).getPackageName(); 250 StatsLogManager.newInstance(mContext).logger() 251 .withPackageName(runningPackage) 252 .log(LAUNCHER_LAUNCH_OMNI_SUCCESSFUL_SYSTEM_ACTION); 253 } 254 }, 200); 255 } 256 }); 257 258 mContext.getSystemService(AccessibilityManager.class).registerSystemAction(new RemoteAction( 259 Icon.createWithResource(mContext, R.drawable.ic_allapps_search), 260 mContext.getString(R.string.search_gesture_feature_title), 261 mContext.getString(R.string.search_gesture_feature_title), 262 searchScreenPendingIntent), 263 SYSTEM_ACTION_ID_SEARCH_SCREEN); 264 } 265 unregisterSearchScreenSystemAction()266 private void unregisterSearchScreenSystemAction() { 267 mContext.getSystemService(AccessibilityManager.class).unregisterSystemAction( 268 SYSTEM_ACTION_ID_SEARCH_SCREEN); 269 } 270 271 /** Dump states. */ dump(String prefix, PrintWriter writer)272 public final void dump(String prefix, PrintWriter writer) { 273 synchronized (mEventLogArray) { 274 mEventLogArray.dump(prefix, writer); 275 } 276 } 277 addEventLog(String event)278 protected final void addEventLog(String event) { 279 synchronized (mEventLogArray) { 280 mEventLogArray.addLog(event); 281 } 282 } 283 addEventLog(String event, boolean extras)284 protected final void addEventLog(String event, boolean extras) { 285 synchronized (mEventLogArray) { 286 mEventLogArray.addLog(event, extras); 287 } 288 } 289 } 290