• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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