fix: 引入Settings的Module
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
* 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.settings.search;
|
||||
|
||||
import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_KEY;
|
||||
import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_SEARCHABLE;
|
||||
import static com.android.settings.core.PreferenceXmlParserUtils.MetadataFlag.FLAG_INCLUDE_PREF_SCREEN;
|
||||
import static com.android.settings.core.PreferenceXmlParserUtils.MetadataFlag.FLAG_NEED_KEY;
|
||||
import static com.android.settings.core.PreferenceXmlParserUtils.MetadataFlag.FLAG_NEED_SEARCHABLE;
|
||||
|
||||
import android.annotation.XmlRes;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.provider.SearchIndexableResource;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.settings.core.BasePreferenceController;
|
||||
import com.android.settings.core.PreferenceControllerListHelper;
|
||||
import com.android.settings.core.PreferenceControllerMixin;
|
||||
import com.android.settings.core.PreferenceXmlParserUtils;
|
||||
import com.android.settingslib.core.AbstractPreferenceController;
|
||||
import com.android.settingslib.search.Indexable;
|
||||
import com.android.settingslib.search.SearchIndexableRaw;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A basic SearchIndexProvider that returns no data to index.
|
||||
*/
|
||||
public class BaseSearchIndexProvider implements Indexable.SearchIndexProvider {
|
||||
|
||||
private static final String TAG = "BaseSearchIndex";
|
||||
private int mXmlRes = 0;
|
||||
|
||||
public BaseSearchIndexProvider() {
|
||||
}
|
||||
|
||||
public BaseSearchIndexProvider(int xmlRes) {
|
||||
mXmlRes = xmlRes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SearchIndexableResource> getXmlResourcesToIndex(Context context, boolean enabled) {
|
||||
if (mXmlRes != 0) {
|
||||
final SearchIndexableResource sir = new SearchIndexableResource(context);
|
||||
sir.xmlResId = mXmlRes;
|
||||
return Arrays.asList(sir);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
|
||||
final List<SearchIndexableRaw> raws = new ArrayList<>();
|
||||
final List<AbstractPreferenceController> controllers = getPreferenceControllers(context);
|
||||
if (controllers == null || controllers.isEmpty()) {
|
||||
return raws;
|
||||
}
|
||||
for (AbstractPreferenceController controller : controllers) {
|
||||
if (controller instanceof PreferenceControllerMixin) {
|
||||
((PreferenceControllerMixin) controller).updateRawDataToIndex(raws);
|
||||
} else if (controller instanceof BasePreferenceController) {
|
||||
((BasePreferenceController) controller).updateRawDataToIndex(raws);
|
||||
}
|
||||
}
|
||||
return raws;
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public List<SearchIndexableRaw> getDynamicRawDataToIndex(Context context, boolean enabled) {
|
||||
final List<SearchIndexableRaw> dynamicRaws = new ArrayList<>();
|
||||
if (!isPageSearchEnabled(context)) {
|
||||
// Entire page should be suppressed, do not add dynamic raw data.
|
||||
return dynamicRaws;
|
||||
}
|
||||
final List<AbstractPreferenceController> controllers = getPreferenceControllers(context);
|
||||
if (controllers == null || controllers.isEmpty()) {
|
||||
return dynamicRaws;
|
||||
}
|
||||
for (AbstractPreferenceController controller : controllers) {
|
||||
if (controller instanceof PreferenceControllerMixin) {
|
||||
((PreferenceControllerMixin) controller).updateDynamicRawDataToIndex(dynamicRaws);
|
||||
} else if (controller instanceof BasePreferenceController) {
|
||||
((BasePreferenceController) controller).updateDynamicRawDataToIndex(dynamicRaws);
|
||||
} else {
|
||||
Log.e(TAG, controller.getClass().getName()
|
||||
+ " must implement " + PreferenceControllerMixin.class.getName()
|
||||
+ " treating the dynamic indexable");
|
||||
}
|
||||
}
|
||||
return dynamicRaws;
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public List<String> getNonIndexableKeys(Context context) {
|
||||
if (!isPageSearchEnabled(context)) {
|
||||
// Entire page should be suppressed, mark all keys from this page as non-indexable.
|
||||
return getNonIndexableKeysFromXml(context, true /* suppressAllPage */);
|
||||
}
|
||||
final List<String> nonIndexableKeys = new ArrayList<>();
|
||||
nonIndexableKeys.addAll(getNonIndexableKeysFromXml(context, false /* suppressAllPage */));
|
||||
final List<AbstractPreferenceController> controllers = getPreferenceControllers(context);
|
||||
if (controllers != null && !controllers.isEmpty()) {
|
||||
for (AbstractPreferenceController controller : controllers) {
|
||||
if (controller instanceof PreferenceControllerMixin) {
|
||||
((PreferenceControllerMixin) controller)
|
||||
.updateNonIndexableKeys(nonIndexableKeys);
|
||||
} else if (controller instanceof BasePreferenceController) {
|
||||
((BasePreferenceController) controller).updateNonIndexableKeys(
|
||||
nonIndexableKeys);
|
||||
} else {
|
||||
Log.e(TAG, controller.getClass().getName()
|
||||
+ " must implement " + PreferenceControllerMixin.class.getName()
|
||||
+ " treating the key non-indexable");
|
||||
nonIndexableKeys.add(controller.getPreferenceKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
return nonIndexableKeys;
|
||||
}
|
||||
|
||||
public List<AbstractPreferenceController> getPreferenceControllers(Context context) {
|
||||
List<AbstractPreferenceController> controllersFromCode = new ArrayList<>();
|
||||
try {
|
||||
controllersFromCode = createPreferenceControllers(context);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Error initializing controller in fragment: " + this + ", e: " + e);
|
||||
}
|
||||
|
||||
final List<SearchIndexableResource> res = getXmlResourcesToIndex(context, true);
|
||||
if (res == null || res.isEmpty()) {
|
||||
return controllersFromCode;
|
||||
}
|
||||
List<BasePreferenceController> controllersFromXml = new ArrayList<>();
|
||||
for (SearchIndexableResource sir : res) {
|
||||
controllersFromXml.addAll(PreferenceControllerListHelper
|
||||
.getPreferenceControllersFromXml(context, sir.xmlResId));
|
||||
}
|
||||
controllersFromXml = PreferenceControllerListHelper.filterControllers(controllersFromXml,
|
||||
controllersFromCode);
|
||||
final List<AbstractPreferenceController> allControllers = new ArrayList<>();
|
||||
if (controllersFromCode != null) {
|
||||
allControllers.addAll(controllersFromCode);
|
||||
}
|
||||
allControllers.addAll(controllersFromXml);
|
||||
return allControllers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a list of {@link AbstractPreferenceController} programatically.
|
||||
* <p/>
|
||||
* This list should create controllers that are not defined in xml as a Slice controller.
|
||||
*/
|
||||
public List<AbstractPreferenceController> createPreferenceControllers(Context context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the page should be considered in search query. If return false, entire page
|
||||
* will be suppressed during search query.
|
||||
*/
|
||||
protected boolean isPageSearchEnabled(Context context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all non-indexable keys from xml. If {@param suppressAllPage} is set, all keys are
|
||||
* considered non-indexable. Otherwise, only keys with searchable="false" are included.
|
||||
*/
|
||||
private List<String> getNonIndexableKeysFromXml(Context context, boolean suppressAllPage) {
|
||||
final List<SearchIndexableResource> resources = getXmlResourcesToIndex(
|
||||
context, true /* not used*/);
|
||||
if (resources == null || resources.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
final List<String> nonIndexableKeys = new ArrayList<>();
|
||||
for (SearchIndexableResource res : resources) {
|
||||
nonIndexableKeys.addAll(
|
||||
getNonIndexableKeysFromXml(context, res.xmlResId, suppressAllPage));
|
||||
}
|
||||
return nonIndexableKeys;
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
|
||||
public List<String> getNonIndexableKeysFromXml(Context context, @XmlRes int xmlResId,
|
||||
boolean suppressAllPage) {
|
||||
return getKeysFromXml(context, xmlResId, suppressAllPage);
|
||||
}
|
||||
|
||||
private List<String> getKeysFromXml(Context context, @XmlRes int xmlResId,
|
||||
boolean suppressAllPage) {
|
||||
final List<String> keys = new ArrayList<>();
|
||||
try {
|
||||
final List<Bundle> metadata = PreferenceXmlParserUtils.extractMetadata(context,
|
||||
xmlResId, FLAG_NEED_KEY | FLAG_INCLUDE_PREF_SCREEN | FLAG_NEED_SEARCHABLE);
|
||||
for (Bundle bundle : metadata) {
|
||||
if (suppressAllPage || !bundle.getBoolean(METADATA_SEARCHABLE, true)) {
|
||||
keys.add(bundle.getString(METADATA_KEY));
|
||||
}
|
||||
}
|
||||
} catch (IOException | XmlPullParserException e) {
|
||||
Log.w(TAG, "Error parsing non-indexable from xml " + xmlResId);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.settings.search;
|
||||
|
||||
import android.util.ArrayMap;
|
||||
|
||||
import com.android.settings.backup.UserBackupSettingsActivity;
|
||||
import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment;
|
||||
import com.android.settings.connecteddevice.usb.UsbDetailsFragment;
|
||||
import com.android.settings.fuelgauge.batteryusage.PowerUsageAdvanced;
|
||||
import com.android.settings.fuelgauge.batteryusage.PowerUsageSummary;
|
||||
import com.android.settings.gestures.GestureNavigationSettingsFragment;
|
||||
import com.android.settings.gestures.SystemNavigationGestureSettings;
|
||||
import com.android.settings.location.LocationSettings;
|
||||
import com.android.settings.location.RecentLocationAccessSeeAllFragment;
|
||||
import com.android.settings.notification.zen.ZenModeBlockedEffectsSettings;
|
||||
import com.android.settings.notification.zen.ZenModeRestrictNotificationsSettings;
|
||||
import com.android.settings.security.SecuritySettings;
|
||||
import com.android.settings.security.screenlock.ScreenLockSettings;
|
||||
import com.android.settings.system.SystemDashboardFragment;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A registry of custom site map.
|
||||
*/
|
||||
public class CustomSiteMapRegistry {
|
||||
|
||||
/**
|
||||
* Map from child class to parent class.
|
||||
*/
|
||||
public static final Map<String, String> CUSTOM_SITE_MAP;
|
||||
|
||||
static {
|
||||
CUSTOM_SITE_MAP = new ArrayMap<>();
|
||||
CUSTOM_SITE_MAP.put(ScreenLockSettings.class.getName(), SecuritySettings.class.getName());
|
||||
CUSTOM_SITE_MAP.put(PowerUsageAdvanced.class.getName(), PowerUsageSummary.class.getName());
|
||||
CUSTOM_SITE_MAP.put(RecentLocationAccessSeeAllFragment.class.getName(),
|
||||
LocationSettings.class.getName());
|
||||
CUSTOM_SITE_MAP.put(UsbDetailsFragment.class.getName(),
|
||||
ConnectedDeviceDashboardFragment.class.getName());
|
||||
CUSTOM_SITE_MAP.put(UserBackupSettingsActivity.class.getName(),
|
||||
SystemDashboardFragment.class.getName());
|
||||
CUSTOM_SITE_MAP.put(ZenModeBlockedEffectsSettings.class.getName(),
|
||||
ZenModeRestrictNotificationsSettings.class.getName());
|
||||
CUSTOM_SITE_MAP.put(GestureNavigationSettingsFragment.class.getName(),
|
||||
SystemNavigationGestureSettings.class.getName());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* 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.settings.search;
|
||||
|
||||
import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO;
|
||||
|
||||
import android.app.ActivityOptions;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.Utils;
|
||||
import com.android.settings.activityembedding.ActivityEmbeddingRulesController;
|
||||
import com.android.settings.overlay.FeatureFactory;
|
||||
import com.android.settingslib.search.SearchIndexableResources;
|
||||
|
||||
import com.google.android.setupcompat.util.WizardManagerHelper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* FeatureProvider for Settings Search
|
||||
*/
|
||||
public interface SearchFeatureProvider {
|
||||
|
||||
String KEY_HOMEPAGE_SEARCH_BAR = "homepage_search_bar";
|
||||
int REQUEST_CODE = 501;
|
||||
|
||||
/**
|
||||
* Ensures the caller has necessary privilege to launch search result page.
|
||||
*
|
||||
* @throws IllegalArgumentException when caller is null
|
||||
* @throws SecurityException when caller is not allowed to launch search result page
|
||||
*/
|
||||
void verifyLaunchSearchResultPageCaller(@NonNull Context context, @NonNull String callerPackage)
|
||||
throws SecurityException, IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* @return a {@link SearchIndexableResources} to be used for indexing search results.
|
||||
*/
|
||||
SearchIndexableResources getSearchIndexableResources();
|
||||
|
||||
/**
|
||||
* @return a package name of settings intelligence.
|
||||
*/
|
||||
default String getSettingsIntelligencePkgName(Context context) {
|
||||
return context.getString(R.string.config_settingsintelligence_package_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the pre-index intent.
|
||||
*/
|
||||
default void sendPreIndexIntent(Context context){
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the search toolbar.
|
||||
*/
|
||||
default void initSearchToolbar(FragmentActivity activity, Toolbar toolbar, int pageId) {
|
||||
if (activity == null || toolbar == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!WizardManagerHelper.isDeviceProvisioned(activity)
|
||||
|| !Utils.isPackageEnabled(activity, getSettingsIntelligencePkgName(activity))
|
||||
|| WizardManagerHelper.isAnySetupWizard(activity.getIntent())) {
|
||||
final ViewGroup parent = (ViewGroup) toolbar.getParent();
|
||||
if (parent != null) {
|
||||
parent.setVisibility(View.GONE);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Please forgive me for what I am about to do.
|
||||
//
|
||||
// Need to make the navigation icon non-clickable so that the entire card is clickable
|
||||
// and goes to the search UI. Also set the background to null so there's no ripple.
|
||||
final View navView = toolbar.getNavigationView();
|
||||
navView.setClickable(false);
|
||||
navView.setFocusable(false);
|
||||
navView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
|
||||
navView.setBackground(null);
|
||||
|
||||
final Context context = activity.getApplicationContext();
|
||||
final Intent intent = buildSearchIntent(context, pageId)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
final List<ResolveInfo> resolveInfos =
|
||||
activity.getPackageManager().queryIntentActivities(intent,
|
||||
PackageManager.MATCH_DEFAULT_ONLY);
|
||||
if (resolveInfos.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ComponentName searchComponentName = resolveInfos.get(0)
|
||||
.getComponentInfo().getComponentName();
|
||||
// Set a component name since activity embedding requires a component name for
|
||||
// registering a rule.
|
||||
intent.setComponent(searchComponentName);
|
||||
ActivityEmbeddingRulesController.registerTwoPanePairRuleForSettingsHome(
|
||||
context,
|
||||
searchComponentName,
|
||||
intent.getAction(),
|
||||
false /* finishPrimaryWithSecondary */,
|
||||
true /* finishSecondaryWithPrimary */,
|
||||
false /* clearTop */);
|
||||
|
||||
toolbar.setOnClickListener(tb -> startSearchActivity(context, activity, pageId, intent));
|
||||
|
||||
toolbar.setHandwritingDelegatorCallback(
|
||||
() -> startSearchActivity(context, activity, pageId, intent));
|
||||
toolbar.setAllowedHandwritingDelegatePackage(intent.getPackage());
|
||||
}
|
||||
|
||||
/** Start the search activity. */
|
||||
private static void startSearchActivity(
|
||||
Context context, FragmentActivity activity, int pageId, Intent intent) {
|
||||
FeatureFactory.getFeatureFactory().getSlicesFeatureProvider()
|
||||
.indexSliceDataAsync(context);
|
||||
|
||||
FeatureFactory.getFeatureFactory().getMetricsFeatureProvider()
|
||||
.logSettingsTileClick(KEY_HOMEPAGE_SEARCH_BAR, pageId);
|
||||
|
||||
final Bundle bundle = ActivityOptions.makeSceneTransitionAnimation(activity).toBundle();
|
||||
activity.startActivity(intent, bundle);
|
||||
}
|
||||
|
||||
Intent buildSearchIntent(Context context, int pageId);
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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.settings.search;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.settingslib.search.SearchIndexableResources;
|
||||
import com.android.settingslib.search.SearchIndexableResourcesMobile;
|
||||
|
||||
/**
|
||||
* FeatureProvider for the refactored search code.
|
||||
*/
|
||||
public class SearchFeatureProviderImpl implements SearchFeatureProvider {
|
||||
|
||||
private SearchIndexableResources mSearchIndexableResources;
|
||||
|
||||
@Override
|
||||
public void verifyLaunchSearchResultPageCaller(@NonNull Context context,
|
||||
@NonNull String callerPackage) {
|
||||
if (TextUtils.isEmpty(callerPackage)) {
|
||||
throw new IllegalArgumentException("ExternalSettingsTrampoline intents "
|
||||
+ "must be called with startActivityForResult");
|
||||
}
|
||||
final boolean isSettingsPackage = TextUtils.equals(callerPackage, context.getPackageName())
|
||||
|| TextUtils.equals(getSettingsIntelligencePkgName(context), callerPackage);
|
||||
final boolean isAllowlistedPackage = isSignatureAllowlisted(context, callerPackage);
|
||||
if (isSettingsPackage || isAllowlistedPackage) {
|
||||
return;
|
||||
}
|
||||
throw new SecurityException("Search result intents must be called with from an "
|
||||
+ "allowlisted package.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchIndexableResources getSearchIndexableResources() {
|
||||
if (mSearchIndexableResources == null) {
|
||||
mSearchIndexableResources = new SearchIndexableResourcesMobile();
|
||||
}
|
||||
return mSearchIndexableResources;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Intent buildSearchIntent(Context context, int pageId) {
|
||||
return new Intent(Settings.ACTION_APP_SEARCH_SETTINGS)
|
||||
.setPackage(getSettingsIntelligencePkgName(context))
|
||||
.putExtra(Intent.EXTRA_REFERRER, buildReferrer(context, pageId));
|
||||
}
|
||||
|
||||
protected boolean isSignatureAllowlisted(Context context, String callerPackage) {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Uri buildReferrer(Context context, int pageId) {
|
||||
return new Uri.Builder()
|
||||
.scheme("android-app")
|
||||
.authority(context.getPackageName())
|
||||
.path(String.valueOf(pageId))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* 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.settings.search;
|
||||
|
||||
import static com.android.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS;
|
||||
import static com.android.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT_TAB;
|
||||
import static com.android.settings.activityembedding.EmbeddedDeepLinkUtils.getTrampolineIntent;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
import android.util.FeatureFlagUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.settings.SettingsActivity;
|
||||
import com.android.settings.SettingsApplication;
|
||||
import com.android.settings.SubSettings;
|
||||
import com.android.settings.activityembedding.ActivityEmbeddingRulesController;
|
||||
import com.android.settings.activityembedding.ActivityEmbeddingUtils;
|
||||
import com.android.settings.core.FeatureFlags;
|
||||
import com.android.settings.homepage.DeepLinkHomepageActivityInternal;
|
||||
import com.android.settings.homepage.SettingsHomepageActivity;
|
||||
import com.android.settings.overlay.FeatureFactory;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
/**
|
||||
* A trampoline activity that launches setting result page.
|
||||
*/
|
||||
public class SearchResultTrampoline extends Activity {
|
||||
|
||||
private static final String TAG = "SearchResultTrampoline";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
final String callerPackage = getLaunchedFromPackage();
|
||||
// First make sure caller has privilege to launch a search result page.
|
||||
FeatureFactory.getFeatureFactory()
|
||||
.getSearchFeatureProvider()
|
||||
.verifyLaunchSearchResultPageCaller(this, callerPackage);
|
||||
// Didn't crash, proceed and launch the result as a subsetting.
|
||||
Intent intent = getIntent();
|
||||
final String highlightMenuKey = intent.getStringExtra(
|
||||
Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY);
|
||||
|
||||
final String fragment = intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT);
|
||||
if (!TextUtils.isEmpty(fragment)) {
|
||||
// Hack to take EXTRA_FRAGMENT_ARG_KEY from intent and set into
|
||||
// EXTRA_SHOW_FRAGMENT_ARGUMENTS. This is necessary because intent could be from
|
||||
// external caller and args may not persisted.
|
||||
final String settingKey = intent.getStringExtra(
|
||||
SettingsActivity.EXTRA_FRAGMENT_ARG_KEY);
|
||||
final int tab = intent.getIntExtra(EXTRA_SHOW_FRAGMENT_TAB, 0);
|
||||
final Bundle args = new Bundle();
|
||||
args.putString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY, settingKey);
|
||||
args.putInt(EXTRA_SHOW_FRAGMENT_TAB, tab);
|
||||
intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
|
||||
|
||||
// Reroute request to SubSetting.
|
||||
intent.setClass(this /* context */, SubSettings.class);
|
||||
} else {
|
||||
// Direct link case
|
||||
final String intentUriString = intent.getStringExtra(
|
||||
Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI);
|
||||
if (TextUtils.isEmpty(intentUriString)) {
|
||||
Log.e(TAG, "No EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI for deep link");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
final Uri data = intent.getParcelableExtra(
|
||||
SettingsHomepageActivity.EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_DATA,
|
||||
Uri.class);
|
||||
try {
|
||||
intent = Intent.parseUri(intentUriString, Intent.URI_INTENT_SCHEME);
|
||||
intent.setData(data);
|
||||
} catch (URISyntaxException e) {
|
||||
Log.e(TAG, "Failed to parse deep link intent: " + e);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
|
||||
|
||||
if (!ActivityEmbeddingUtils.isEmbeddingActivityEnabled(this)
|
||||
|| ActivityEmbeddingUtils.isAlreadyEmbedded(this)) {
|
||||
startActivity(intent);
|
||||
} else if (isSettingsIntelligence(callerPackage)) {
|
||||
if (FeatureFlagUtils.isEnabled(this, FeatureFlags.SETTINGS_SEARCH_ALWAYS_EXPAND)) {
|
||||
startActivity(getTrampolineIntent(intent, highlightMenuKey)
|
||||
.setClass(this, DeepLinkHomepageActivityInternal.class)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS));
|
||||
} else {
|
||||
// Register SplitPairRule for SubSettings, set clearTop false to prevent unexpected
|
||||
// back navigation behavior.
|
||||
ActivityEmbeddingRulesController.registerSubSettingsPairRule(this,
|
||||
false /* clearTop */);
|
||||
|
||||
intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
|
||||
// Pass menu key to homepage
|
||||
final SettingsHomepageActivity homeActivity =
|
||||
((SettingsApplication) getApplicationContext()).getHomeActivity();
|
||||
if (homeActivity != null) {
|
||||
homeActivity.getMainFragment().setHighlightMenuKey(highlightMenuKey,
|
||||
/* scrollNeeded= */ true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Two-pane case
|
||||
startActivity(getTrampolineIntent(intent, highlightMenuKey)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||
}
|
||||
|
||||
// Done.
|
||||
finish();
|
||||
}
|
||||
|
||||
private boolean isSettingsIntelligence(String callerPackage) {
|
||||
return TextUtils.equals(
|
||||
callerPackage,
|
||||
FeatureFactory.getFeatureFactory().getSearchFeatureProvider()
|
||||
.getSettingsIntelligencePkgName(this));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.settings.search;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.text.TextUtils;
|
||||
import android.util.FeatureFlagUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.settings.SettingsApplication;
|
||||
import com.android.settings.core.FeatureFlags;
|
||||
import com.android.settings.homepage.SettingsHomepageActivity;
|
||||
|
||||
/**
|
||||
* A broadcast receiver that monitors the search state to show/hide the menu highlight
|
||||
*/
|
||||
public class SearchStateReceiver extends BroadcastReceiver {
|
||||
|
||||
private static final String TAG = "SearchStateReceiver";
|
||||
private static final String ACTION_SEARCH_START = "com.android.settings.SEARCH_START";
|
||||
private static final String ACTION_SEARCH_EXIT = "com.android.settings.SEARCH_EXIT";
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (FeatureFlagUtils.isEnabled(context, FeatureFlags.SETTINGS_SEARCH_ALWAYS_EXPAND)) {
|
||||
// Not needed to show/hide the highlight when search is full screen
|
||||
return;
|
||||
}
|
||||
|
||||
if (intent == null) {
|
||||
Log.w(TAG, "Null intent");
|
||||
return;
|
||||
}
|
||||
|
||||
final SettingsHomepageActivity homeActivity =
|
||||
((SettingsApplication) context.getApplicationContext()).getHomeActivity();
|
||||
if (homeActivity == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String action = intent.getAction();
|
||||
Log.d(TAG, "action: " + action);
|
||||
if (TextUtils.equals(ACTION_SEARCH_START, action)) {
|
||||
homeActivity.getMainFragment().setMenuHighlightShowed(false);
|
||||
} else if (TextUtils.equals(ACTION_SEARCH_EXIT, action)) {
|
||||
homeActivity.getMainFragment().setMenuHighlightShowed(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,477 @@
|
||||
/*
|
||||
* 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.settings.search;
|
||||
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_CLASS_NAME;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_ENTRIES;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_ICON_RESID;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_INTENT_ACTION;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_INTENT_TARGET_CLASS;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_INTENT_TARGET_PACKAGE;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_KEY;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_KEYWORDS;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_SCREEN_TITLE;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_SUMMARY_OFF;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_SUMMARY_ON;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_TITLE;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_USER_ID;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_CLASS_NAME;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_ICON_RESID;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_ACTION;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_RANK;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_RESID;
|
||||
import static android.provider.SearchIndexablesContract.INDEXABLES_RAW_COLUMNS;
|
||||
import static android.provider.SearchIndexablesContract.INDEXABLES_XML_RES_COLUMNS;
|
||||
import static android.provider.SearchIndexablesContract.NON_INDEXABLES_KEYS_COLUMNS;
|
||||
import static android.provider.SearchIndexablesContract.SITE_MAP_COLUMNS;
|
||||
import static android.provider.SearchIndexablesContract.SLICE_URI_PAIRS_COLUMNS;
|
||||
|
||||
import static com.android.settings.dashboard.DashboardFragmentRegistry.CATEGORY_KEY_TO_PARENT_MAP;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.SearchIndexableResource;
|
||||
import android.provider.SearchIndexablesContract;
|
||||
import android.provider.SearchIndexablesProvider;
|
||||
import android.provider.SettingsSlicesContract;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.slice.SliceViewManager;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.SettingsActivity;
|
||||
import com.android.settings.dashboard.CategoryManager;
|
||||
import com.android.settings.dashboard.DashboardFeatureProvider;
|
||||
import com.android.settings.dashboard.DashboardFragmentRegistry;
|
||||
import com.android.settings.overlay.FeatureFactory;
|
||||
import com.android.settings.slices.SettingsSliceProvider;
|
||||
import com.android.settingslib.drawer.ActivityTile;
|
||||
import com.android.settingslib.drawer.DashboardCategory;
|
||||
import com.android.settingslib.drawer.Tile;
|
||||
import com.android.settingslib.search.Indexable;
|
||||
import com.android.settingslib.search.SearchIndexableData;
|
||||
import com.android.settingslib.search.SearchIndexableRaw;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class SettingsSearchIndexablesProvider extends SearchIndexablesProvider {
|
||||
|
||||
public static final boolean DEBUG = false;
|
||||
|
||||
/**
|
||||
* Flag for a system property which checks if we should crash if there are issues in the
|
||||
* indexing pipeline.
|
||||
*/
|
||||
public static final String SYSPROP_CRASH_ON_ERROR =
|
||||
"debug.com.android.settings.search.crash_on_error";
|
||||
|
||||
private static final String TAG = "SettingsSearchProvider";
|
||||
|
||||
private static final Collection<String> INVALID_KEYS;
|
||||
|
||||
// Search enabled states for injection (key: category key, value: search enabled)
|
||||
private Map<String, Boolean> mSearchEnabledByCategoryKeyMap;
|
||||
|
||||
static {
|
||||
INVALID_KEYS = new ArraySet<>();
|
||||
INVALID_KEYS.add(null);
|
||||
INVALID_KEYS.add("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
mSearchEnabledByCategoryKeyMap = new ArrayMap<>();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryXmlResources(String[] projection) {
|
||||
final MatrixCursor cursor = new MatrixCursor(INDEXABLES_XML_RES_COLUMNS);
|
||||
final List<SearchIndexableResource> resources =
|
||||
getSearchIndexableResourcesFromProvider(getContext());
|
||||
for (SearchIndexableResource val : resources) {
|
||||
final Object[] ref = new Object[INDEXABLES_XML_RES_COLUMNS.length];
|
||||
ref[COLUMN_INDEX_XML_RES_RANK] = val.rank;
|
||||
ref[COLUMN_INDEX_XML_RES_RESID] = val.xmlResId;
|
||||
ref[COLUMN_INDEX_XML_RES_CLASS_NAME] = val.className;
|
||||
ref[COLUMN_INDEX_XML_RES_ICON_RESID] = val.iconResId;
|
||||
ref[COLUMN_INDEX_XML_RES_INTENT_ACTION] = val.intentAction;
|
||||
ref[COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE] = val.intentTargetPackage;
|
||||
ref[COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS] = null; // intent target class
|
||||
cursor.addRow(ref);
|
||||
}
|
||||
|
||||
return cursor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a Cursor of RawData. We use those data in search indexing time
|
||||
*/
|
||||
@Override
|
||||
public Cursor queryRawData(String[] projection) {
|
||||
final MatrixCursor cursor = new MatrixCursor(INDEXABLES_RAW_COLUMNS);
|
||||
final List<SearchIndexableRaw> raws = getSearchIndexableRawFromProvider(getContext());
|
||||
for (SearchIndexableRaw val : raws) {
|
||||
cursor.addRow(createIndexableRawColumnObjects(val));
|
||||
}
|
||||
|
||||
return cursor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a combined list non-indexable keys that come from providers inside of settings.
|
||||
* The non-indexable keys are used in Settings search at both index and update time to verify
|
||||
* the validity of results in the database.
|
||||
*/
|
||||
@Override
|
||||
public Cursor queryNonIndexableKeys(String[] projection) {
|
||||
final MatrixCursor cursor = new MatrixCursor(NON_INDEXABLES_KEYS_COLUMNS);
|
||||
final List<String> nonIndexableKeys = getNonIndexableKeysFromProvider(getContext());
|
||||
for (String nik : nonIndexableKeys) {
|
||||
final Object[] ref = new Object[NON_INDEXABLES_KEYS_COLUMNS.length];
|
||||
ref[COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE] = nik;
|
||||
cursor.addRow(ref);
|
||||
}
|
||||
|
||||
return cursor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a Cursor of dynamic Raw data similar to queryRawData. We use those data in search query
|
||||
* time
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public Cursor queryDynamicRawData(String[] projection) {
|
||||
final Context context = getContext();
|
||||
final List<SearchIndexableRaw> rawList = new ArrayList<>();
|
||||
final Collection<SearchIndexableData> bundles = FeatureFactory.getFeatureFactory()
|
||||
.getSearchFeatureProvider().getSearchIndexableResources().getProviderValues();
|
||||
|
||||
for (SearchIndexableData bundle : bundles) {
|
||||
rawList.addAll(getDynamicSearchIndexableRawData(context, bundle));
|
||||
|
||||
// Refresh the search enabled state for indexing injection raw data
|
||||
final Indexable.SearchIndexProvider provider = bundle.getSearchIndexProvider();
|
||||
if (provider instanceof BaseSearchIndexProvider) {
|
||||
refreshSearchEnabledState(context, (BaseSearchIndexProvider) provider);
|
||||
}
|
||||
}
|
||||
rawList.addAll(getInjectionIndexableRawData(context));
|
||||
|
||||
final MatrixCursor cursor = new MatrixCursor(INDEXABLES_RAW_COLUMNS);
|
||||
for (SearchIndexableRaw raw : rawList) {
|
||||
cursor.addRow(createIndexableRawColumnObjects(raw));
|
||||
}
|
||||
|
||||
return cursor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor querySiteMapPairs() {
|
||||
final MatrixCursor cursor = new MatrixCursor(SITE_MAP_COLUMNS);
|
||||
final Context context = getContext();
|
||||
// Loop through all IA categories and pages and build additional SiteMapPairs
|
||||
final List<DashboardCategory> categories = FeatureFactory.getFeatureFactory()
|
||||
.getDashboardFeatureProvider().getAllCategories();
|
||||
for (DashboardCategory category : categories) {
|
||||
// Use the category key to look up parent (which page hosts this key)
|
||||
final String parentClass = CATEGORY_KEY_TO_PARENT_MAP.get(category.key);
|
||||
if (parentClass == null) {
|
||||
continue;
|
||||
}
|
||||
// Build parent-child class pairs for all children listed under this key.
|
||||
for (Tile tile : category.getTiles()) {
|
||||
String childClass = null;
|
||||
CharSequence childTitle = "";
|
||||
if (tile.getMetaData() != null) {
|
||||
childClass = tile.getMetaData().getString(
|
||||
SettingsActivity.META_DATA_KEY_FRAGMENT_CLASS);
|
||||
}
|
||||
if (childClass == null) {
|
||||
childClass = tile.getComponentName();
|
||||
childTitle = tile.getTitle(getContext());
|
||||
}
|
||||
if (childClass == null) {
|
||||
continue;
|
||||
}
|
||||
cursor.newRow()
|
||||
.add(SearchIndexablesContract.SiteMapColumns.PARENT_CLASS, parentClass)
|
||||
.add(SearchIndexablesContract.SiteMapColumns.CHILD_CLASS, childClass)
|
||||
.add(SearchIndexablesContract.SiteMapColumns.CHILD_TITLE, childTitle);
|
||||
}
|
||||
}
|
||||
|
||||
// Loop through custom site map registry to build additional SiteMapPairs
|
||||
for (String childClass : CustomSiteMapRegistry.CUSTOM_SITE_MAP.keySet()) {
|
||||
final String parentClass = CustomSiteMapRegistry.CUSTOM_SITE_MAP.get(childClass);
|
||||
cursor.newRow()
|
||||
.add(SearchIndexablesContract.SiteMapColumns.PARENT_CLASS, parentClass)
|
||||
.add(SearchIndexablesContract.SiteMapColumns.CHILD_CLASS, childClass);
|
||||
}
|
||||
// Done.
|
||||
return cursor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor querySliceUriPairs() {
|
||||
final SliceViewManager manager = SliceViewManager.getInstance(getContext());
|
||||
final MatrixCursor cursor = new MatrixCursor(SLICE_URI_PAIRS_COLUMNS);
|
||||
final String queryUri = getContext().getString(R.string.config_non_public_slice_query_uri);
|
||||
final Uri baseUri = !TextUtils.isEmpty(queryUri) ? Uri.parse(queryUri)
|
||||
: new Uri.Builder()
|
||||
.scheme(ContentResolver.SCHEME_CONTENT)
|
||||
.authority(SettingsSliceProvider.SLICE_AUTHORITY)
|
||||
.build();
|
||||
|
||||
final Uri platformBaseUri =
|
||||
new Uri.Builder()
|
||||
.scheme(ContentResolver.SCHEME_CONTENT)
|
||||
.authority(SettingsSlicesContract.AUTHORITY)
|
||||
.build();
|
||||
|
||||
final Collection<Uri> sliceUris = manager.getSliceDescendants(baseUri);
|
||||
sliceUris.addAll(manager.getSliceDescendants(platformBaseUri));
|
||||
|
||||
for (Uri uri : sliceUris) {
|
||||
cursor.newRow()
|
||||
.add(SearchIndexablesContract.SliceUriPairColumns.KEY, uri.getLastPathSegment())
|
||||
.add(SearchIndexablesContract.SliceUriPairColumns.SLICE_URI, uri);
|
||||
}
|
||||
|
||||
return cursor;
|
||||
}
|
||||
|
||||
private List<String> getNonIndexableKeysFromProvider(Context context) {
|
||||
final Collection<SearchIndexableData> bundles = FeatureFactory.getFeatureFactory()
|
||||
.getSearchFeatureProvider().getSearchIndexableResources().getProviderValues();
|
||||
|
||||
final List<String> nonIndexableKeys = new ArrayList<>();
|
||||
|
||||
for (SearchIndexableData bundle : bundles) {
|
||||
final long startTime = System.currentTimeMillis();
|
||||
Indexable.SearchIndexProvider provider = bundle.getSearchIndexProvider();
|
||||
List<String> providerNonIndexableKeys;
|
||||
try {
|
||||
providerNonIndexableKeys = provider.getNonIndexableKeys(context);
|
||||
} catch (Exception e) {
|
||||
// Catch a generic crash. In the absence of the catch, the background thread will
|
||||
// silently fail anyway, so we aren't losing information by catching the exception.
|
||||
// We crash when the system property exists so that we can test if crashes need to
|
||||
// be fixed.
|
||||
// The gain is that if there is a crash in a specific controller, we don't lose all
|
||||
// non-indexable keys, but we can still find specific crashes in development.
|
||||
if (System.getProperty(SYSPROP_CRASH_ON_ERROR) != null) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
Log.e(TAG, "Error trying to get non-indexable keys from: "
|
||||
+ bundle.getTargetClass().getName(), e);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (providerNonIndexableKeys == null || providerNonIndexableKeys.isEmpty()) {
|
||||
if (DEBUG) {
|
||||
final long totalTime = System.currentTimeMillis() - startTime;
|
||||
Log.d(TAG, "No indexable, total time " + totalTime);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (providerNonIndexableKeys.removeAll(INVALID_KEYS)) {
|
||||
Log.v(TAG, provider + " tried to add an empty non-indexable key");
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
final long totalTime = System.currentTimeMillis() - startTime;
|
||||
Log.d(TAG, "Non-indexables " + providerNonIndexableKeys.size() + ", total time "
|
||||
+ totalTime);
|
||||
}
|
||||
|
||||
nonIndexableKeys.addAll(providerNonIndexableKeys);
|
||||
}
|
||||
|
||||
return nonIndexableKeys;
|
||||
}
|
||||
|
||||
private List<SearchIndexableResource> getSearchIndexableResourcesFromProvider(Context context) {
|
||||
final Collection<SearchIndexableData> bundles = FeatureFactory.getFeatureFactory()
|
||||
.getSearchFeatureProvider().getSearchIndexableResources().getProviderValues();
|
||||
List<SearchIndexableResource> resourceList = new ArrayList<>();
|
||||
|
||||
for (SearchIndexableData bundle : bundles) {
|
||||
Indexable.SearchIndexProvider provider = bundle.getSearchIndexProvider();
|
||||
final List<SearchIndexableResource> resList =
|
||||
provider.getXmlResourcesToIndex(context, true);
|
||||
|
||||
if (resList == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (SearchIndexableResource item : resList) {
|
||||
item.className = TextUtils.isEmpty(item.className)
|
||||
? bundle.getTargetClass().getName()
|
||||
: item.className;
|
||||
}
|
||||
|
||||
resourceList.addAll(resList);
|
||||
}
|
||||
|
||||
return resourceList;
|
||||
}
|
||||
|
||||
private List<SearchIndexableRaw> getSearchIndexableRawFromProvider(Context context) {
|
||||
final Collection<SearchIndexableData> bundles = FeatureFactory.getFeatureFactory()
|
||||
.getSearchFeatureProvider().getSearchIndexableResources().getProviderValues();
|
||||
final List<SearchIndexableRaw> rawList = new ArrayList<>();
|
||||
|
||||
for (SearchIndexableData bundle : bundles) {
|
||||
Indexable.SearchIndexProvider provider = bundle.getSearchIndexProvider();
|
||||
final List<SearchIndexableRaw> providerRaws = provider.getRawDataToIndex(context,
|
||||
true /* enabled */);
|
||||
|
||||
if (providerRaws == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (SearchIndexableRaw raw : providerRaws) {
|
||||
// The classname and intent information comes from the PreIndexData
|
||||
// This will be more clear when provider conversion is done at PreIndex time.
|
||||
raw.className = bundle.getTargetClass().getName();
|
||||
}
|
||||
rawList.addAll(providerRaws);
|
||||
}
|
||||
|
||||
return rawList;
|
||||
}
|
||||
|
||||
private List<SearchIndexableRaw> getDynamicSearchIndexableRawData(Context context,
|
||||
SearchIndexableData bundle) {
|
||||
final Indexable.SearchIndexProvider provider = bundle.getSearchIndexProvider();
|
||||
final List<SearchIndexableRaw> providerRaws =
|
||||
provider.getDynamicRawDataToIndex(context, true /* enabled */);
|
||||
if (providerRaws == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
for (SearchIndexableRaw raw : providerRaws) {
|
||||
// The classname and intent information comes from the PreIndexData
|
||||
// This will be more clear when provider conversion is done at PreIndex time.
|
||||
raw.className = bundle.getTargetClass().getName();
|
||||
}
|
||||
return providerRaws;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
List<SearchIndexableRaw> getInjectionIndexableRawData(Context context) {
|
||||
final DashboardFeatureProvider dashboardFeatureProvider =
|
||||
FeatureFactory.getFeatureFactory().getDashboardFeatureProvider();
|
||||
final List<SearchIndexableRaw> rawList = new ArrayList<>();
|
||||
final String currentPackageName = context.getPackageName();
|
||||
for (DashboardCategory category : dashboardFeatureProvider.getAllCategories()) {
|
||||
if (mSearchEnabledByCategoryKeyMap.containsKey(category.key)
|
||||
&& !mSearchEnabledByCategoryKeyMap.get(category.key)) {
|
||||
Log.i(TAG, "Skip indexing category: " + category.key);
|
||||
continue;
|
||||
}
|
||||
for (Tile tile : category.getTiles()) {
|
||||
if (!isEligibleForIndexing(currentPackageName, tile)) {
|
||||
continue;
|
||||
}
|
||||
final SearchIndexableRaw raw = new SearchIndexableRaw(context);
|
||||
final CharSequence title = tile.getTitle(context);
|
||||
raw.title = TextUtils.isEmpty(title) ? null : title.toString();
|
||||
if (TextUtils.isEmpty(raw.title)) {
|
||||
continue;
|
||||
}
|
||||
raw.key = dashboardFeatureProvider.getDashboardKeyForTile(tile);
|
||||
final CharSequence summary = tile.getSummary(context);
|
||||
raw.summaryOn = TextUtils.isEmpty(summary) ? null : summary.toString();
|
||||
raw.summaryOff = raw.summaryOn;
|
||||
raw.className = CATEGORY_KEY_TO_PARENT_MAP.get(tile.getCategory());
|
||||
rawList.add(raw);
|
||||
}
|
||||
}
|
||||
|
||||
return rawList;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void refreshSearchEnabledState(Context context, BaseSearchIndexProvider provider) {
|
||||
// Provider's class name is like "com.android.settings.Settings$1"
|
||||
String className = provider.getClass().getName();
|
||||
final int delimiter = className.lastIndexOf("$");
|
||||
if (delimiter > 0) {
|
||||
// Parse the outer class name of this provider
|
||||
className = className.substring(0, delimiter);
|
||||
}
|
||||
|
||||
// Lookup the category key by the class name
|
||||
final String categoryKey = DashboardFragmentRegistry.PARENT_TO_CATEGORY_KEY_MAP
|
||||
.get(className);
|
||||
if (categoryKey == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final DashboardCategory category = CategoryManager.get(context)
|
||||
.getTilesByCategory(context, categoryKey);
|
||||
if (category != null) {
|
||||
mSearchEnabledByCategoryKeyMap.put(category.key, provider.isPageSearchEnabled(context));
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
boolean isEligibleForIndexing(String packageName, Tile tile) {
|
||||
if (TextUtils.equals(packageName, tile.getPackageName())
|
||||
&& tile instanceof ActivityTile) {
|
||||
// Skip Settings injected items because they should be indexed in the sub-pages.
|
||||
return false;
|
||||
}
|
||||
return tile.isSearchable();
|
||||
}
|
||||
|
||||
private static Object[] createIndexableRawColumnObjects(SearchIndexableRaw raw) {
|
||||
final Object[] ref = new Object[INDEXABLES_RAW_COLUMNS.length];
|
||||
ref[COLUMN_INDEX_RAW_TITLE] = raw.title;
|
||||
ref[COLUMN_INDEX_RAW_SUMMARY_ON] = raw.summaryOn;
|
||||
ref[COLUMN_INDEX_RAW_SUMMARY_OFF] = raw.summaryOff;
|
||||
ref[COLUMN_INDEX_RAW_ENTRIES] = raw.entries;
|
||||
ref[COLUMN_INDEX_RAW_KEYWORDS] = raw.keywords;
|
||||
ref[COLUMN_INDEX_RAW_SCREEN_TITLE] = raw.screenTitle;
|
||||
ref[COLUMN_INDEX_RAW_CLASS_NAME] = raw.className;
|
||||
ref[COLUMN_INDEX_RAW_ICON_RESID] = raw.iconResId;
|
||||
ref[COLUMN_INDEX_RAW_INTENT_ACTION] = raw.intentAction;
|
||||
ref[COLUMN_INDEX_RAW_INTENT_TARGET_PACKAGE] = raw.intentTargetPackage;
|
||||
ref[COLUMN_INDEX_RAW_INTENT_TARGET_CLASS] = raw.intentTargetClass;
|
||||
ref[COLUMN_INDEX_RAW_KEY] = raw.key;
|
||||
ref[COLUMN_INDEX_RAW_USER_ID] = raw.userId;
|
||||
return ref;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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.settings.search.actionbar;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.Utils;
|
||||
import com.android.settings.core.InstrumentedFragment;
|
||||
import com.android.settings.core.InstrumentedPreferenceFragment;
|
||||
import com.android.settings.overlay.FeatureFactory;
|
||||
import com.android.settings.search.SearchFeatureProvider;
|
||||
import com.android.settingslib.core.lifecycle.LifecycleObserver;
|
||||
import com.android.settingslib.core.lifecycle.events.OnCreateOptionsMenu;
|
||||
|
||||
import com.google.android.setupcompat.util.WizardManagerHelper;
|
||||
|
||||
public class SearchMenuController implements LifecycleObserver, OnCreateOptionsMenu {
|
||||
|
||||
public static final String NEED_SEARCH_ICON_IN_ACTION_BAR = "need_search_icon_in_action_bar";
|
||||
public static final int MENU_SEARCH = Menu.FIRST + 10;
|
||||
|
||||
private final Fragment mHost;
|
||||
private final int mPageId;
|
||||
|
||||
public static void init(@NonNull InstrumentedPreferenceFragment host) {
|
||||
host.getSettingsLifecycle().addObserver(
|
||||
new SearchMenuController(host, host.getMetricsCategory()));
|
||||
}
|
||||
|
||||
public static void init(@NonNull InstrumentedFragment host) {
|
||||
host.getSettingsLifecycle().addObserver(
|
||||
new SearchMenuController(host, host.getMetricsCategory()));
|
||||
}
|
||||
|
||||
private SearchMenuController(@NonNull Fragment host, int pageId) {
|
||||
mHost = host;
|
||||
mPageId = pageId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
final Activity activity = mHost.getActivity();
|
||||
final String SettingsIntelligencePkgName = activity.getString(
|
||||
R.string.config_settingsintelligence_package_name);
|
||||
if (!WizardManagerHelper.isDeviceProvisioned(activity)
|
||||
|| WizardManagerHelper.isAnySetupWizard(activity.getIntent())) {
|
||||
return;
|
||||
}
|
||||
if (!Utils.isPackageEnabled(activity, SettingsIntelligencePkgName)) {
|
||||
return;
|
||||
}
|
||||
if (menu == null) {
|
||||
return;
|
||||
}
|
||||
final Bundle arguments = mHost.getArguments();
|
||||
if (arguments != null && !arguments.getBoolean(NEED_SEARCH_ICON_IN_ACTION_BAR, true)) {
|
||||
return;
|
||||
}
|
||||
// menu contains search item, skip it
|
||||
if (menu.findItem(MENU_SEARCH) != null) {
|
||||
return;
|
||||
}
|
||||
final MenuItem searchItem = menu.add(Menu.NONE, MENU_SEARCH, 0 /* order */,
|
||||
com.android.settingslib.search.widget.R.string.search_menu);
|
||||
searchItem.setIcon(com.android.settingslib.search.widget.R.drawable.ic_search_24dp);
|
||||
searchItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||
|
||||
searchItem.setOnMenuItemClickListener(target -> {
|
||||
final Intent intent = FeatureFactory.getFeatureFactory()
|
||||
.getSearchFeatureProvider()
|
||||
.buildSearchIntent(activity, mPageId);
|
||||
|
||||
if (activity.getPackageManager().queryIntentActivities(intent,
|
||||
PackageManager.MATCH_DEFAULT_ONLY).isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
FeatureFactory.getFeatureFactory().getMetricsFeatureProvider()
|
||||
.action(activity, SettingsEnums.ACTION_SEARCH_RESULTS);
|
||||
mHost.startActivityForResult(intent, SearchFeatureProvider.REQUEST_CODE);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user