Merge "Changed how the Smart Suggestions service is defined."
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d367afc..74ec0b9 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -12838,6 +12838,23 @@
                 "max_sound_trigger_detection_service_ops_per_day";
 
         /**
+         * Property used by {@code com.android.server.SystemServer} on start to decide whether
+         * the Smart Suggestions service should be created or not
+         *
+         * <p>By default it should *NOT* be set (in which case the decision is based on whether
+         * the OEM provides an implementation for the service), but it can be overridden to:
+         *
+         * <ul>
+         *   <li>Provide a "kill switch" so OEMs can disable it remotely in case of emergency.
+         *   <li>Enable the CTS tests to be run on AOSP builds
+         * </ul>
+         *
+         * @hide
+         */
+        public static final String SMART_SUGGESTIONS_SERVICE_EXPLICITLY_ENABLED =
+                "smart_suggestions_service_explicitly_enabled";
+
+        /**
          * Settings to backup. This is here so that it's in the same place as the settings
          * keys and easy to update.
          *
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index f22d57f..4c1fc5c 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -761,6 +761,13 @@
     }
     optional SmartSelection smart_selection = 108;
 
+    message SmartSuggestions {
+      option (android.msg_privacy).dest = DEST_EXPLICIT;
+
+      optional SettingProto service_explicitly_enabled = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
+    }
+    optional SmartSuggestions smart_suggestions = 145;
+
     message Sms {
         option (android.msg_privacy).dest = DEST_EXPLICIT;
 
@@ -993,5 +1000,5 @@
 
     // Please insert fields in alphabetical order and group them into messages
     // if possible (to avoid reaching the method limit).
-    // Next tag = 145;
+    // Next tag = 146;
 }
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 0cd6bc5..c62071b 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3350,6 +3350,14 @@
     -->
     <string name="config_defaultTextClassifierPackage" translatable="false"></string>
 
+    <!-- The package name for the system's smart suggestion service.
+         This service must be trusted, as it can be activated without explicit consent of the user.
+         If no service with the specified name exists on the device, content capture and
+         smart suggestions will be disabled.
+         Example: "com.android.intelligence/.SmartSuggestionsService"
+    -->
+    <string name="config_defaultSmartSuggestionsService" translatable="false"></string>
+
     <!-- Whether the device uses the default focus highlight when focus state isn't specified. -->
     <bool name="config_useDefaultFocusHighlight">true</bool>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 01422c8..6854a84e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3265,6 +3265,7 @@
   <java-symbol type="string" name="notification_channel_do_not_disturb" />
   <java-symbol type="string" name="config_defaultAutofillService" />
   <java-symbol type="string" name="config_defaultTextClassifierPackage" />
+  <java-symbol type="string" name="config_defaultSmartSuggestionsService" />
 
   <java-symbol type="string" name="notification_channel_foreground_service" />
   <java-symbol type="string" name="foreground_service_app_in_background" />
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 8a27de4b..ed9c3d5 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -415,6 +415,7 @@
                     Settings.Global.SHOW_TEMPERATURE_WARNING,
                     Settings.Global.SMART_SELECTION_UPDATE_CONTENT_URL,
                     Settings.Global.SMART_SELECTION_UPDATE_METADATA_URL,
+                    Settings.Global.SMART_SUGGESTIONS_SERVICE_EXPLICITLY_ENABLED,
                     Settings.Global.SMS_ACCESS_RESTRICTION_ENABLED,
                     Settings.Global.SMS_OUTGOING_CHECK_INTERVAL_MS,
                     Settings.Global.SMS_OUTGOING_CHECK_MAX_COUNT,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index d1824d7..df5b146 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1165,6 +1165,12 @@
                 GlobalSettingsProto.SmartSelection.UPDATE_METADATA_URL);
         p.end(smartSelectToken);
 
+        final long smartSuggestionsToken = p.start(GlobalSettingsProto.SMART_SUGGESTIONS);
+        dumpSetting(s, p,
+                Settings.Global.SMART_SUGGESTIONS_SERVICE_EXPLICITLY_ENABLED,
+                GlobalSettingsProto.SmartSuggestions.SERVICE_EXPLICITLY_ENABLED);
+        p.end(smartSuggestionsToken);
+
         final long smsToken = p.start(GlobalSettingsProto.SMS);
         dumpSetting(s, p,
                 Settings.Global.SMS_OUTGOING_CHECK_INTERVAL_MS,
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 0df99d4..18bc856 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -190,6 +190,11 @@
         return mInfo.getServiceInfo();
     }
 
+    @Override // from PerUserSystemService
+    protected String getDefaultComponentName() {
+        return getComponentNameFromSettings();
+    }
+
     @Nullable
     String[] getUrlBarResourceIdsForCompatMode(@NonNull String packageName) {
         return mAutofillCompatState.getUrlBarResourceIds(packageName, mUserId);
@@ -369,7 +374,7 @@
 
         final long identity = Binder.clearCallingIdentity();
         try {
-            final String autoFillService = getComponentNameFromSettings();
+            final String autoFillService = getComponentNameLocked();
             final ComponentName componentName = serviceInfo.getComponentName();
             if (componentName.equals(ComponentName.unflattenFromString(autoFillService))) {
                 mMetricsLogger.action(MetricsEvent.AUTOFILL_SERVICE_DISABLED_SELF,
diff --git a/services/core/java/com/android/server/AbstractMasterSystemService.java b/services/core/java/com/android/server/AbstractMasterSystemService.java
index 1759ce1..76010b3 100644
--- a/services/core/java/com/android/server/AbstractMasterSystemService.java
+++ b/services/core/java/com/android/server/AbstractMasterSystemService.java
@@ -39,6 +39,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.Preconditions;
 
 import java.io.PrintWriter;
 import java.util.List;
@@ -211,6 +212,66 @@
     }
 
     /**
+     * Temporary sets the service implementation.
+     *
+     * <p>Typically used by Shell command and/or CTS tests.
+     *
+     * @param componentName name of the new component
+     * @param durationMs how long the change will be valid (the service will be automatically reset
+     *            to the default component after this timeout expires).
+     * @throws SecurityException if caller is not allowed to manage this service's settings.
+     * @throws IllegalArgumentException if value of {@code durationMs} is higher than
+     *             {@link #getMaximumTemporaryServiceDurationMs()}.
+     */
+    public final void setTemporaryService(@UserIdInt int userId, @NonNull String componentName,
+            int durationMs) {
+        Slog.i(mTag, "setTemporaryService(" + userId + ") to " + componentName + " for "
+                + durationMs + "ms");
+        enforceCallingPermissionForManagement();
+
+        Preconditions.checkNotNull(componentName);
+        final int maxDurationMs = getMaximumTemporaryServiceDurationMs();
+        if (durationMs > maxDurationMs) {
+            throw new IllegalArgumentException(
+                    "Max duration is " + maxDurationMs + " (called with " + durationMs + ")");
+        }
+
+        synchronized (mLock) {
+            final S service = getServiceForUserLocked(userId);
+            if (service != null) {
+                service.setTemporaryServiceLocked(componentName, durationMs);
+            }
+        }
+    }
+
+    /**
+     * Gets the maximum time the service implementation can be changed.
+     *
+     * @throws UnsupportedOperationException if subclass doesn't override it.
+     */
+    protected int getMaximumTemporaryServiceDurationMs() {
+        throw new UnsupportedOperationException("Not implemented by " + getClass());
+    }
+
+    /**
+     * Resets the temporary service implementation to the default component.
+     *
+     * <p>Typically used by Shell command and/or CTS tests.
+     *
+     * @throws SecurityException if caller is not allowed to manage this service's settings.
+     */
+    public final void resetTemporaryService(@UserIdInt int userId) {
+        Slog.i(mTag, "resetTemporaryService(): " + userId);
+        enforceCallingPermissionForManagement();
+        synchronized (mLock) {
+            final S service = getServiceForUserLocked(userId);
+            if (service != null) {
+                service.resetTemporaryServiceLocked();
+            }
+        }
+    }
+
+    /**
      * Asserts that the caller has permissions to manage this service.
      *
      * <p>Typically called by {@code ShellCommand} implementations.
diff --git a/services/core/java/com/android/server/AbstractPerUserSystemService.java b/services/core/java/com/android/server/AbstractPerUserSystemService.java
index 001d85f..a26102d 100644
--- a/services/core/java/com/android/server/AbstractPerUserSystemService.java
+++ b/services/core/java/com/android/server/AbstractPerUserSystemService.java
@@ -26,12 +26,17 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ServiceInfo;
 import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.UserManager;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Slog;
+import android.util.TimeUtils;
 
 import com.android.internal.annotations.GuardedBy;
 
@@ -49,6 +54,9 @@
 public abstract class AbstractPerUserSystemService<S extends AbstractPerUserSystemService<S, M>,
         M extends AbstractMasterSystemService<M, S>> {
 
+    /** Handler message to {@link #resetTemporaryServiceLocked()} */
+    private static final int MSG_RESET_TEMPORARY_SERVICE = 0;
+
     protected final @UserIdInt int mUserId;
     protected final Object mLock;
     protected final String mTag = getClass().getSimpleName();
@@ -70,6 +78,26 @@
     @GuardedBy("mLock")
     private ServiceInfo mServiceInfo;
 
+    /**
+     * Temporary service name set by {@link #setTemporaryServiceLocked(String, int)}.
+     *
+     * <p>Typically used by Shell command and/or CTS tests.
+     */
+    @GuardedBy("mLock")
+    private String mTemporaryServiceName;
+
+    /**
+     * When the temporary service will expire (and reset back to the default).
+     */
+    @GuardedBy("mLock")
+    private long mTemporaryServiceExpiration;
+
+    /**
+     * Handler used to reset the temporary service name.
+     */
+    @GuardedBy("mLock")
+    private Handler mTemporaryHandler;
+
     protected AbstractPerUserSystemService(@NonNull M master, @NonNull Object lock,
             @UserIdInt int userId) {
         mMaster = master;
@@ -130,7 +158,7 @@
         mDisabled = disabled;
         ComponentName serviceComponent = null;
         ServiceInfo serviceInfo = null;
-        final String componentName = getComponentNameFromSettings();
+        final String componentName = getComponentNameLocked();
         if (!TextUtils.isEmpty(componentName)) {
             try {
                 serviceComponent = ComponentName.unflattenFromString(componentName);
@@ -191,6 +219,29 @@
     }
 
     /**
+     * Gets the current name of the service, which is either the
+     *  {@link #getDefaultComponentName() default service} or the
+     *  {@link #setTemporaryServiceLocked(String, int) temporary one}.
+     */
+    protected final String getComponentNameLocked() {
+        if (mTemporaryServiceName != null) {
+            // Always log it, as it should only be used on CTS or during development
+            Slog.w(mTag, "getComponentName(): using temporary name " + mTemporaryServiceName);
+            return mTemporaryServiceName;
+        }
+        return getDefaultComponentName();
+    }
+
+    /**
+     * Gets the name of the default component for the service.
+     *
+     * <p>Typically implemented by returning {@link #getComponentNameFromSettings()} or by using
+     * a string from the system resources.
+     */
+    @Nullable
+    protected abstract String getDefaultComponentName();
+
+    /**
      * Gets this name of the remote service this service binds to as defined by {@link Settings}.
      */
     @Nullable
@@ -201,6 +252,66 @@
     }
 
     /**
+     * Checks whether the current service for the user was temporarily set.
+     */
+    public final boolean isTemporaryServiceSetLocked() {
+        return mTemporaryServiceName != null;
+    }
+
+    /**
+     * Temporary sets the service implementation.
+     *
+     * @param componentName name of the new component
+     * @param durationMs how long the change will be valid (the service will be automatically reset
+     * to the default component after this timeout expires).
+     */
+    protected final void setTemporaryServiceLocked(@NonNull String componentName, int durationMs) {
+        mTemporaryServiceName = componentName;
+
+        if (mTemporaryHandler == null) {
+            mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) {
+                @Override
+                public void handleMessage(Message msg) {
+                    if (msg.what == MSG_RESET_TEMPORARY_SERVICE) {
+                        synchronized (mLock) {
+                            resetTemporaryServiceLocked();
+                        }
+                    } else {
+                        Slog.wtf(mTag, "invalid handler msg: " + msg);
+                    }
+                }
+            };
+        } else {
+            removeResetTemporaryServiceMessageLocked();
+        }
+        mTemporaryServiceExpiration = SystemClock.elapsedRealtime() + durationMs;
+        mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE, durationMs);
+
+        updateLocked(mDisabled);
+    }
+
+    private void removeResetTemporaryServiceMessageLocked() {
+        if (mMaster.verbose) {
+            Slog.v(mTag, "setTemporaryServiceLocked(): removing old message");
+        }
+        // NOTE: caller should already have checked it
+        mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
+    }
+
+    /**
+     * Resets the temporary service implementation to the default component.
+     */
+    protected final void resetTemporaryServiceLocked() {
+        Slog.i(mTag, "resetting temporary service from " + mTemporaryServiceName);
+        mTemporaryServiceName = null;
+        if (mTemporaryHandler != null) {
+            removeResetTemporaryServiceMessageLocked();
+            mTemporaryHandler = null;
+        }
+        updateLocked(mDisabled);
+    }
+
+    /**
      * Gets the {@link ComponentName} of the remote service this service binds to, or {@code null}
      * if the service is disabled.
      */
@@ -290,12 +401,14 @@
             pw.print(prefix); pw.print("Service UID: ");
             pw.println(mServiceInfo.applicationInfo.uid);
         }
-        final String componentName = getComponentNameFromSettings();
-        if (componentName != null) {
-            pw.print(prefix); pw.print("Service name: ");
-            pw.println(componentName);
+        if (mTemporaryServiceName != null) {
+            pw.print(prefix); pw.print("Temporary service name: "); pw.print(mTemporaryServiceName);
+            final long ttl = mTemporaryServiceExpiration - SystemClock.elapsedRealtime();
+            pw.print(" (expires in "); TimeUtils.formatDuration(ttl, pw); pw.println(")");
+            pw.print(prefix); pw.print(prefix);
+            pw.print("Default service name: "); pw.println(getDefaultComponentName());
         } else {
-            pw.println("No service package set");
+            pw.print(prefix); pw.print("Service name: "); pw.println(getDefaultComponentName());
         }
     }
 }
diff --git a/services/intelligence/java/com/android/server/intelligence/IntelligenceManagerService.java b/services/intelligence/java/com/android/server/intelligence/IntelligenceManagerService.java
index 4c68064..b8f2ad0 100644
--- a/services/intelligence/java/com/android/server/intelligence/IntelligenceManagerService.java
+++ b/services/intelligence/java/com/android/server/intelligence/IntelligenceManagerService.java
@@ -65,6 +65,8 @@
 
     static final String RECEIVER_BUNDLE_EXTRA_SESSIONS = "sessions";
 
+    private static final int MAX_TEMP_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes
+
     @GuardedBy("mLock")
     private ActivityManagerInternal mAm;
 
@@ -75,12 +77,6 @@
     }
 
     @Override // from AbstractMasterSystemService
-    protected String getServiceSettingsProperty() {
-        // TODO(b/111276913): STOPSHIP temporary settings, until it's set by resourcs + cmd
-        return "smart_suggestions_service";
-    }
-
-    @Override // from AbstractMasterSystemService
     protected IntelligencePerUserService newServiceLocked(@UserIdInt int resolvedUserId,
             boolean disabled) {
         return new IntelligencePerUserService(this, mLock, resolvedUserId);
@@ -104,6 +100,11 @@
         getContext().enforceCallingPermission(MANAGE_SMART_SUGGESTIONS, TAG);
     }
 
+    @Override // from AbstractMasterSystemService
+    protected int getMaximumTemporaryServiceDurationMs() {
+        return MAX_TEMP_SERVICE_DURATION_MS;
+    }
+
     // Called by Shell command.
     void destroySessions(@UserIdInt int userId, @NonNull IResultReceiver receiver) {
         Slog.i(TAG, "destroySessions() for userId " + userId);
diff --git a/services/intelligence/java/com/android/server/intelligence/IntelligencePerUserService.java b/services/intelligence/java/com/android/server/intelligence/IntelligencePerUserService.java
index dbf8601..6f047c5 100644
--- a/services/intelligence/java/com/android/server/intelligence/IntelligencePerUserService.java
+++ b/services/intelligence/java/com/android/server/intelligence/IntelligencePerUserService.java
@@ -36,6 +36,7 @@
 import android.os.RemoteException;
 import android.service.intelligence.InteractionSessionId;
 import android.service.intelligence.SnapshotData;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Slog;
 import android.view.autofill.AutofillId;
@@ -77,17 +78,24 @@
     protected ServiceInfo newServiceInfo(@NonNull ComponentName serviceComponent)
             throws NameNotFoundException {
 
+        int flags = PackageManager.GET_META_DATA;
+        final boolean isTemp = isTemporaryServiceSetLocked();
+        if (!isTemp) {
+            flags |= PackageManager.MATCH_SYSTEM_ONLY;
+        }
+
         ServiceInfo si;
         try {
-            // TODO(b/111276913): must check that either the service is from a system component,
-            // or it matches a service set by shell cmd (so it can be used on CTS tests and when
-            // OEMs are implementing the real service
-            si = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
-                    PackageManager.GET_META_DATA, mUserId);
+            si = AppGlobals.getPackageManager().getServiceInfo(serviceComponent, flags, mUserId);
         } catch (RemoteException e) {
             Slog.w(TAG, "Could not get service for " + serviceComponent + ": " + e);
             return null;
         }
+        if (si == null) {
+            Slog.w(TAG, "Could not get serviceInfo for " + (isTemp ? " (temp)" : "(default system)")
+                    + " " + serviceComponent.flattenToShortString());
+            return null;
+        }
         if (!Manifest.permission.BIND_SMART_SUGGESTIONS_SERVICE.equals(si.permission)) {
             Slog.w(TAG, "SmartSuggestionsService from '" + si.packageName
                     + "' does not require permission "
@@ -105,6 +113,13 @@
         return super.updateLocked(disabled);
     }
 
+    @Override // from PerUserSystemService
+    protected String getDefaultComponentName() {
+        final String name = getContext()
+                .getString(com.android.internal.R.string.config_defaultSmartSuggestionsService);
+        return TextUtils.isEmpty(name) ? null : name;
+    }
+
     // TODO(b/111276913): log metrics
     @GuardedBy("mLock")
     public void startSessionLocked(@NonNull IBinder activityToken,
diff --git a/services/intelligence/java/com/android/server/intelligence/IntelligenceServiceShellCommand.java b/services/intelligence/java/com/android/server/intelligence/IntelligenceServiceShellCommand.java
index b7c1f78..0d92a972 100644
--- a/services/intelligence/java/com/android/server/intelligence/IntelligenceServiceShellCommand.java
+++ b/services/intelligence/java/com/android/server/intelligence/IntelligenceServiceShellCommand.java
@@ -75,6 +75,10 @@
             pw.println("  set bind-instant-service-allowed [true | false]");
             pw.println("    Sets whether binding to services provided by instant apps is allowed");
             pw.println("");
+            pw.println("  set temporary-service USER_ID [COMPONENT_NAME DURATION]");
+            pw.println("    Temporarily (for DURATION ms) changes the service implemtation.");
+            pw.println("    To reset, call with just the USER_ID argument.");
+            pw.println("");
             pw.println("  list sessions [--user USER_ID]");
             pw.println("    Lists all pending sessions.");
             pw.println("");
@@ -101,6 +105,8 @@
         switch(what) {
             case "bind-instant-service-allowed":
                 return setBindInstantService(pw);
+            case "temporary-service":
+                return setTemporaryService();
             default:
                 pw.println("Invalid set: " + what);
                 return -1;
@@ -131,6 +137,18 @@
         }
     }
 
+    private int setTemporaryService() {
+        final int userId = getNextIntArgRequired();
+        final String serviceName = getNextArg();
+        if (serviceName == null) {
+            mService.resetTemporaryService(userId);
+            return 0;
+        }
+        final int duration = getNextIntArgRequired();
+        mService.setTemporaryService(userId, serviceName, duration);
+        return 0;
+    }
+
     private int requestDestroy(PrintWriter pw) {
         if (!isNextArgSessions(pw)) {
             return -1;
@@ -204,4 +222,8 @@
         }
         return UserHandle.USER_ALL;
     }
+
+    private int getNextIntArgRequired() {
+        return Integer.parseInt(getNextArgRequired());
+    }
 }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index dadaf2a..05ff660 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -22,6 +22,7 @@
 import static android.os.IServiceManager.DUMP_FLAG_PROTO;
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import android.annotation.NonNull;
 import android.app.ActivityThread;
 import android.app.INotificationManager;
 import android.app.usage.UsageStatsManagerInternal;
@@ -55,7 +56,9 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.storage.IStorageManager;
+import android.provider.Settings;
 import android.sysprop.VoldProperties;
+import android.text.TextUtils;
 import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.Slog;
@@ -798,10 +801,6 @@
         boolean disableSystemTextClassifier = SystemProperties.getBoolean(
                 "config.disable_systemtextclassifier", false);
 
-        //TODO(b/111276913): temporarily disabled until the manager is properly implemented to
-        // ignore events when disabled and buffer when enabled
-        boolean disableIntelligence = SystemProperties.getBoolean(
-                "config.disable_intelligence", true);
         boolean disableNetworkTime = SystemProperties.getBoolean("config.disable_networktime",
                 false);
         boolean disableCameraService = SystemProperties.getBoolean("config.disable_cameraservice",
@@ -1137,13 +1136,7 @@
                 traceEnd();
             }
 
-            if (!disableIntelligence) {
-                traceBeginAndSlog("StartIntelligenceService");
-                mSystemServiceManager.startService(INTELLIGENCE_MANAGER_SERVICE_CLASS);
-                traceEnd();
-            } else {
-                Slog.d(TAG, "IntelligenceService disabled");
-            }
+            startIntelligenceService(context);
 
             // NOTE: ClipboardService indirectly depends on IntelligenceService
             traceBeginAndSlog("StartClipboardService");
@@ -2107,6 +2100,37 @@
         }, BOOT_TIMINGS_TRACE_LOG);
     }
 
+    private void startIntelligenceService(@NonNull Context context) {
+
+        // First check if it was explicitly enabled by Settings
+        boolean explicitlySupported = false;
+        final String settings = Settings.Global.getString(context.getContentResolver(),
+                Settings.Global.SMART_SUGGESTIONS_SERVICE_EXPLICITLY_ENABLED);
+        if (settings != null) {
+            explicitlySupported = Boolean.parseBoolean(settings);
+            if (explicitlySupported) {
+                Slog.d(TAG, "IntelligenceService explicitly enabled by Settings");
+            } else {
+                Slog.d(TAG, "IntelligenceService explicitly disabled by Settings");
+                return;
+            }
+        }
+
+        // Then check if OEM overlaid the resource that defines the service.
+        if (!explicitlySupported) {
+            final String serviceName = context
+                    .getString(com.android.internal.R.string.config_defaultSmartSuggestionsService);
+            if (TextUtils.isEmpty(serviceName)) {
+                Slog.d(TAG, "IntelligenceService disabled because config resource is not overlaid");
+                return;
+            }
+        }
+
+        traceBeginAndSlog("StartIntelligenceService");
+        mSystemServiceManager.startService(INTELLIGENCE_MANAGER_SERVICE_CLASS);
+        traceEnd();
+    }
+
     static final void startSystemUi(Context context, WindowManagerService windowManager) {
         Intent intent = new Intent();
         intent.setComponent(new ComponentName("com.android.systemui",