Loading packages/SettingsProvider/AndroidManifest.xml +5 −0 Original line number Diff line number Diff line Loading @@ -21,5 +21,10 @@ android:singleUser="true" android:initOrder="100" android:visibleToInstantApps="true" /> <service android:name="WriteFallbackSettingsFilesJobService" android:permission="android.permission.BIND_JOB_SERVICE" android:exported="true"/> </application> </manifest> packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +79 −3 Original line number Diff line number Diff line Loading @@ -23,12 +23,16 @@ import static android.os.Process.SYSTEM_UID; import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVERLAY; import static com.android.providers.settings.SettingsState.FALLBACK_FILE_SUFFIX; import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.AppGlobals; import android.app.backup.BackupManager; import android.app.job.JobInfo; import android.app.job.JobScheduler; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentProvider; Loading @@ -55,12 +59,14 @@ import android.os.Build; import android.os.Bundle; import android.os.DropBoxManager; import android.os.Environment; import android.os.FileUtils; import android.os.Handler; import android.os.HandlerThread; import android.os.IUserRestrictionsListener; import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteCallback; import android.os.RemoteException; Loading Loading @@ -95,6 +101,7 @@ import libcore.util.HexEncoding; import java.io.File; import java.io.FileDescriptor; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; import java.nio.ByteBuffer; import java.security.InvalidKeyException; Loading Loading @@ -154,9 +161,11 @@ public class SettingsProvider extends ContentProvider { private static final String LOG_TAG = "SettingsProvider"; private static final String TABLE_SYSTEM = "system"; private static final String TABLE_SECURE = "secure"; private static final String TABLE_GLOBAL = "global"; public static final String TABLE_SYSTEM = "system"; public static final String TABLE_SECURE = "secure"; public static final String TABLE_GLOBAL = "global"; public static final String TABLE_SSAID = "ssaid"; public static final String TABLE_CONFIG = "config"; // Old tables no longer exist. private static final String TABLE_FAVORITES = "favorites"; Loading Loading @@ -205,6 +214,10 @@ public class SettingsProvider extends ContentProvider { public static final String RESULT_ROWS_DELETED = "result_rows_deleted"; public static final String RESULT_SETTINGS_LIST = "result_settings_list"; // Used for scheduling jobs to make a copy for the settings files public static final int WRITE_FALLBACK_SETTINGS_FILES_JOB_ID = 1; public static final long ONE_DAY_INTERVAL_MILLIS = 24 * 60 * 60 * 1000L; // Overlay specified settings whitelisted for Instant Apps private static final Set<String> OVERLAY_ALLOWED_GLOBAL_INSTANT_APP_SETTINGS = new ArraySet<>(); private static final Set<String> OVERLAY_ALLOWED_SYSTEM_INSTANT_APP_SETTINGS = new ArraySet<>(); Loading Loading @@ -2484,6 +2497,68 @@ public class SettingsProvider extends ContentProvider { } } /** * Schedule the job service to make a copy of all the settings files. */ public void scheduleWriteFallbackFilesJob() { final Context context = getContext(); final JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); if (jobScheduler == null) { // Might happen: SettingsProvider is created before JobSchedulerService in system server return; } // Check if the job is already scheduled. If so, skip scheduling another one if (jobScheduler.getPendingJob(WRITE_FALLBACK_SETTINGS_FILES_JOB_ID) != null) { return; } // Back up all settings files final PersistableBundle bundle = new PersistableBundle(); final File globalSettingsFile = mSettingsRegistry.getSettingsFile( makeKey(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM)); final File systemSettingsFile = mSettingsRegistry.getSettingsFile( makeKey(SETTINGS_TYPE_SYSTEM, UserHandle.USER_SYSTEM)); final File secureSettingsFile = mSettingsRegistry.getSettingsFile( makeKey(SETTINGS_TYPE_SECURE, UserHandle.USER_SYSTEM)); final File ssaidSettingsFile = mSettingsRegistry.getSettingsFile( makeKey(SETTINGS_TYPE_SSAID, UserHandle.USER_SYSTEM)); final File configSettingsFile = mSettingsRegistry.getSettingsFile( makeKey(SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM)); bundle.putString(TABLE_GLOBAL, globalSettingsFile.getAbsolutePath()); bundle.putString(TABLE_SYSTEM, systemSettingsFile.getAbsolutePath()); bundle.putString(TABLE_SECURE, secureSettingsFile.getAbsolutePath()); bundle.putString(TABLE_SSAID, ssaidSettingsFile.getAbsolutePath()); bundle.putString(TABLE_CONFIG, configSettingsFile.getAbsolutePath()); // Schedule the job to write the fallback files, once daily when phone is charging jobScheduler.schedule(new JobInfo.Builder(WRITE_FALLBACK_SETTINGS_FILES_JOB_ID, new ComponentName(context, WriteFallbackSettingsFilesJobService.class)) .setExtras(bundle) .setPeriodic(ONE_DAY_INTERVAL_MILLIS) .setRequiresCharging(true) .setPersisted(true) .build()); } /** * For each file in the given list, if it exists, copy it to a back up file. Ignore failures. * @param filePaths List of paths of files that need to be backed up */ public static void writeFallBackSettingsFiles(List<String> filePaths) { final int numFiles = filePaths.size(); for (int i = 0; i < numFiles; i++) { final String filePath = filePaths.get(i); final File originalFile = new File(filePath); if (SettingsState.stateFileExists(originalFile)) { final File fallBackFile = new File(filePath + FALLBACK_FILE_SUFFIX); try { FileUtils.copy(originalFile, fallBackFile); } catch (IOException ex) { Slog.w(LOG_TAG, "Failed to write fallback file for: " + filePath); } } } } final class SettingsRegistry { private static final String DROPBOX_TAG_USERLOG = "restricted_profile_ssaid"; Loading Loading @@ -3431,6 +3506,7 @@ public class SettingsProvider extends ContentProvider { case MSG_NOTIFY_DATA_CHANGED: { mBackupManager.dataChanged(); scheduleWriteFallbackFilesJob(); } break; } } Loading packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +42 −7 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import android.content.pm.PackageManager; import android.content.pm.Signature; import android.os.Binder; import android.os.Build; import android.os.FileUtils; import android.os.Handler; import android.os.Looper; import android.os.Message; Loading Loading @@ -101,6 +102,8 @@ final class SettingsState { public static final int VERSION_UNDEFINED = -1; public static final String FALLBACK_FILE_SUFFIX = ".fallback"; private static final String TAG_SETTINGS = "settings"; private static final String TAG_SETTING = "setting"; private static final String ATTR_PACKAGE = "package"; Loading Loading @@ -266,7 +269,7 @@ final class SettingsState { public SettingsState(Context context, Object lock, File file, int key, int maxBytesPerAppPackage, Looper looper) { // It is important that we use the same lock as the settings provider // to ensure multiple mutations on this state are atomicaly persisted // to ensure multiple mutations on this state are atomically persisted // as the async persistence should be blocked while we make changes. mContext = context; mLock = lock; Loading Loading @@ -998,24 +1001,56 @@ final class SettingsState { } @GuardedBy("mLock") private void readStateSyncLocked() { private void readStateSyncLocked() throws IllegalStateException { FileInputStream in; AtomicFile file = new AtomicFile(mStatePersistFile); try { in = new AtomicFile(mStatePersistFile).openRead(); in = file.openRead(); } catch (FileNotFoundException fnfe) { Slog.i(LOG_TAG, "No settings state " + mStatePersistFile); Slog.w(LOG_TAG, "No settings state " + mStatePersistFile); logSettingsDirectoryInformation(mStatePersistFile); addHistoricalOperationLocked(HISTORICAL_OPERATION_INITIALIZE, null); return; } if (parseStateFromXmlStreamLocked(in)) { return; } // Settings file exists but is corrupted. Retry with the fallback file final File statePersistFallbackFile = new File( mStatePersistFile.getAbsolutePath() + FALLBACK_FILE_SUFFIX); Slog.i(LOG_TAG, "Failed parsing settings file: " + mStatePersistFile + ", retrying with fallback file: " + statePersistFallbackFile); try { in = new AtomicFile(statePersistFallbackFile).openRead(); } catch (FileNotFoundException fnfe) { final String message = "No fallback file found for: " + mStatePersistFile; Slog.wtf(LOG_TAG, message); throw new IllegalStateException(message); } if (parseStateFromXmlStreamLocked(in)) { // Parsed state from fallback file. Restore original file with fallback file try { FileUtils.copy(statePersistFallbackFile, mStatePersistFile); } catch (IOException ignored) { // Failed to copy, but it's okay because we already parsed states from fallback file } } else { final String message = "Failed parsing settings file: " + mStatePersistFile; Slog.wtf(LOG_TAG, message); throw new IllegalStateException(message); } } @GuardedBy("mLock") private boolean parseStateFromXmlStreamLocked(FileInputStream in) { try { XmlPullParser parser = Xml.newPullParser(); parser.setInput(in, StandardCharsets.UTF_8.name()); parseStateLocked(parser); return true; } catch (XmlPullParserException | IOException e) { String message = "Failed parsing settings file: " + mStatePersistFile; Slog.wtf(LOG_TAG, message); throw new IllegalStateException(message, e); return false; } finally { IoUtils.closeQuietly(in); } Loading packages/SettingsProvider/src/com/android/providers/settings/WriteFallbackSettingsFilesJobService.java 0 → 100644 +58 −0 Original line number Diff line number Diff line /* * 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.providers.settings; import static com.android.providers.settings.SettingsProvider.TABLE_CONFIG; import static com.android.providers.settings.SettingsProvider.TABLE_GLOBAL; import static com.android.providers.settings.SettingsProvider.TABLE_SECURE; import static com.android.providers.settings.SettingsProvider.TABLE_SSAID; import static com.android.providers.settings.SettingsProvider.TABLE_SYSTEM; import static com.android.providers.settings.SettingsProvider.WRITE_FALLBACK_SETTINGS_FILES_JOB_ID; import android.app.job.JobParameters; import android.app.job.JobService; import java.util.ArrayList; import java.util.List; /** * JobService to make a copy of a list of files, given their paths. */ public class WriteFallbackSettingsFilesJobService extends JobService { @Override public boolean onStartJob(final JobParameters params) { switch (params.getJobId()) { case WRITE_FALLBACK_SETTINGS_FILES_JOB_ID: final List<String> settingsFiles = new ArrayList<>(); settingsFiles.add(params.getExtras().getString(TABLE_GLOBAL, "")); settingsFiles.add(params.getExtras().getString(TABLE_SYSTEM, "")); settingsFiles.add(params.getExtras().getString(TABLE_SECURE, "")); settingsFiles.add(params.getExtras().getString(TABLE_SSAID, "")); settingsFiles.add(params.getExtras().getString(TABLE_CONFIG, "")); SettingsProvider.writeFallBackSettingsFiles(settingsFiles); return true; default: return false; } } @Override public boolean onStopJob(JobParameters params) { return false; } } Loading
packages/SettingsProvider/AndroidManifest.xml +5 −0 Original line number Diff line number Diff line Loading @@ -21,5 +21,10 @@ android:singleUser="true" android:initOrder="100" android:visibleToInstantApps="true" /> <service android:name="WriteFallbackSettingsFilesJobService" android:permission="android.permission.BIND_JOB_SERVICE" android:exported="true"/> </application> </manifest>
packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +79 −3 Original line number Diff line number Diff line Loading @@ -23,12 +23,16 @@ import static android.os.Process.SYSTEM_UID; import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVERLAY; import static com.android.providers.settings.SettingsState.FALLBACK_FILE_SUFFIX; import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.AppGlobals; import android.app.backup.BackupManager; import android.app.job.JobInfo; import android.app.job.JobScheduler; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentProvider; Loading @@ -55,12 +59,14 @@ import android.os.Build; import android.os.Bundle; import android.os.DropBoxManager; import android.os.Environment; import android.os.FileUtils; import android.os.Handler; import android.os.HandlerThread; import android.os.IUserRestrictionsListener; import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteCallback; import android.os.RemoteException; Loading Loading @@ -95,6 +101,7 @@ import libcore.util.HexEncoding; import java.io.File; import java.io.FileDescriptor; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; import java.nio.ByteBuffer; import java.security.InvalidKeyException; Loading Loading @@ -154,9 +161,11 @@ public class SettingsProvider extends ContentProvider { private static final String LOG_TAG = "SettingsProvider"; private static final String TABLE_SYSTEM = "system"; private static final String TABLE_SECURE = "secure"; private static final String TABLE_GLOBAL = "global"; public static final String TABLE_SYSTEM = "system"; public static final String TABLE_SECURE = "secure"; public static final String TABLE_GLOBAL = "global"; public static final String TABLE_SSAID = "ssaid"; public static final String TABLE_CONFIG = "config"; // Old tables no longer exist. private static final String TABLE_FAVORITES = "favorites"; Loading Loading @@ -205,6 +214,10 @@ public class SettingsProvider extends ContentProvider { public static final String RESULT_ROWS_DELETED = "result_rows_deleted"; public static final String RESULT_SETTINGS_LIST = "result_settings_list"; // Used for scheduling jobs to make a copy for the settings files public static final int WRITE_FALLBACK_SETTINGS_FILES_JOB_ID = 1; public static final long ONE_DAY_INTERVAL_MILLIS = 24 * 60 * 60 * 1000L; // Overlay specified settings whitelisted for Instant Apps private static final Set<String> OVERLAY_ALLOWED_GLOBAL_INSTANT_APP_SETTINGS = new ArraySet<>(); private static final Set<String> OVERLAY_ALLOWED_SYSTEM_INSTANT_APP_SETTINGS = new ArraySet<>(); Loading Loading @@ -2484,6 +2497,68 @@ public class SettingsProvider extends ContentProvider { } } /** * Schedule the job service to make a copy of all the settings files. */ public void scheduleWriteFallbackFilesJob() { final Context context = getContext(); final JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); if (jobScheduler == null) { // Might happen: SettingsProvider is created before JobSchedulerService in system server return; } // Check if the job is already scheduled. If so, skip scheduling another one if (jobScheduler.getPendingJob(WRITE_FALLBACK_SETTINGS_FILES_JOB_ID) != null) { return; } // Back up all settings files final PersistableBundle bundle = new PersistableBundle(); final File globalSettingsFile = mSettingsRegistry.getSettingsFile( makeKey(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM)); final File systemSettingsFile = mSettingsRegistry.getSettingsFile( makeKey(SETTINGS_TYPE_SYSTEM, UserHandle.USER_SYSTEM)); final File secureSettingsFile = mSettingsRegistry.getSettingsFile( makeKey(SETTINGS_TYPE_SECURE, UserHandle.USER_SYSTEM)); final File ssaidSettingsFile = mSettingsRegistry.getSettingsFile( makeKey(SETTINGS_TYPE_SSAID, UserHandle.USER_SYSTEM)); final File configSettingsFile = mSettingsRegistry.getSettingsFile( makeKey(SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM)); bundle.putString(TABLE_GLOBAL, globalSettingsFile.getAbsolutePath()); bundle.putString(TABLE_SYSTEM, systemSettingsFile.getAbsolutePath()); bundle.putString(TABLE_SECURE, secureSettingsFile.getAbsolutePath()); bundle.putString(TABLE_SSAID, ssaidSettingsFile.getAbsolutePath()); bundle.putString(TABLE_CONFIG, configSettingsFile.getAbsolutePath()); // Schedule the job to write the fallback files, once daily when phone is charging jobScheduler.schedule(new JobInfo.Builder(WRITE_FALLBACK_SETTINGS_FILES_JOB_ID, new ComponentName(context, WriteFallbackSettingsFilesJobService.class)) .setExtras(bundle) .setPeriodic(ONE_DAY_INTERVAL_MILLIS) .setRequiresCharging(true) .setPersisted(true) .build()); } /** * For each file in the given list, if it exists, copy it to a back up file. Ignore failures. * @param filePaths List of paths of files that need to be backed up */ public static void writeFallBackSettingsFiles(List<String> filePaths) { final int numFiles = filePaths.size(); for (int i = 0; i < numFiles; i++) { final String filePath = filePaths.get(i); final File originalFile = new File(filePath); if (SettingsState.stateFileExists(originalFile)) { final File fallBackFile = new File(filePath + FALLBACK_FILE_SUFFIX); try { FileUtils.copy(originalFile, fallBackFile); } catch (IOException ex) { Slog.w(LOG_TAG, "Failed to write fallback file for: " + filePath); } } } } final class SettingsRegistry { private static final String DROPBOX_TAG_USERLOG = "restricted_profile_ssaid"; Loading Loading @@ -3431,6 +3506,7 @@ public class SettingsProvider extends ContentProvider { case MSG_NOTIFY_DATA_CHANGED: { mBackupManager.dataChanged(); scheduleWriteFallbackFilesJob(); } break; } } Loading
packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +42 −7 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import android.content.pm.PackageManager; import android.content.pm.Signature; import android.os.Binder; import android.os.Build; import android.os.FileUtils; import android.os.Handler; import android.os.Looper; import android.os.Message; Loading Loading @@ -101,6 +102,8 @@ final class SettingsState { public static final int VERSION_UNDEFINED = -1; public static final String FALLBACK_FILE_SUFFIX = ".fallback"; private static final String TAG_SETTINGS = "settings"; private static final String TAG_SETTING = "setting"; private static final String ATTR_PACKAGE = "package"; Loading Loading @@ -266,7 +269,7 @@ final class SettingsState { public SettingsState(Context context, Object lock, File file, int key, int maxBytesPerAppPackage, Looper looper) { // It is important that we use the same lock as the settings provider // to ensure multiple mutations on this state are atomicaly persisted // to ensure multiple mutations on this state are atomically persisted // as the async persistence should be blocked while we make changes. mContext = context; mLock = lock; Loading Loading @@ -998,24 +1001,56 @@ final class SettingsState { } @GuardedBy("mLock") private void readStateSyncLocked() { private void readStateSyncLocked() throws IllegalStateException { FileInputStream in; AtomicFile file = new AtomicFile(mStatePersistFile); try { in = new AtomicFile(mStatePersistFile).openRead(); in = file.openRead(); } catch (FileNotFoundException fnfe) { Slog.i(LOG_TAG, "No settings state " + mStatePersistFile); Slog.w(LOG_TAG, "No settings state " + mStatePersistFile); logSettingsDirectoryInformation(mStatePersistFile); addHistoricalOperationLocked(HISTORICAL_OPERATION_INITIALIZE, null); return; } if (parseStateFromXmlStreamLocked(in)) { return; } // Settings file exists but is corrupted. Retry with the fallback file final File statePersistFallbackFile = new File( mStatePersistFile.getAbsolutePath() + FALLBACK_FILE_SUFFIX); Slog.i(LOG_TAG, "Failed parsing settings file: " + mStatePersistFile + ", retrying with fallback file: " + statePersistFallbackFile); try { in = new AtomicFile(statePersistFallbackFile).openRead(); } catch (FileNotFoundException fnfe) { final String message = "No fallback file found for: " + mStatePersistFile; Slog.wtf(LOG_TAG, message); throw new IllegalStateException(message); } if (parseStateFromXmlStreamLocked(in)) { // Parsed state from fallback file. Restore original file with fallback file try { FileUtils.copy(statePersistFallbackFile, mStatePersistFile); } catch (IOException ignored) { // Failed to copy, but it's okay because we already parsed states from fallback file } } else { final String message = "Failed parsing settings file: " + mStatePersistFile; Slog.wtf(LOG_TAG, message); throw new IllegalStateException(message); } } @GuardedBy("mLock") private boolean parseStateFromXmlStreamLocked(FileInputStream in) { try { XmlPullParser parser = Xml.newPullParser(); parser.setInput(in, StandardCharsets.UTF_8.name()); parseStateLocked(parser); return true; } catch (XmlPullParserException | IOException e) { String message = "Failed parsing settings file: " + mStatePersistFile; Slog.wtf(LOG_TAG, message); throw new IllegalStateException(message, e); return false; } finally { IoUtils.closeQuietly(in); } Loading
packages/SettingsProvider/src/com/android/providers/settings/WriteFallbackSettingsFilesJobService.java 0 → 100644 +58 −0 Original line number Diff line number Diff line /* * 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.providers.settings; import static com.android.providers.settings.SettingsProvider.TABLE_CONFIG; import static com.android.providers.settings.SettingsProvider.TABLE_GLOBAL; import static com.android.providers.settings.SettingsProvider.TABLE_SECURE; import static com.android.providers.settings.SettingsProvider.TABLE_SSAID; import static com.android.providers.settings.SettingsProvider.TABLE_SYSTEM; import static com.android.providers.settings.SettingsProvider.WRITE_FALLBACK_SETTINGS_FILES_JOB_ID; import android.app.job.JobParameters; import android.app.job.JobService; import java.util.ArrayList; import java.util.List; /** * JobService to make a copy of a list of files, given their paths. */ public class WriteFallbackSettingsFilesJobService extends JobService { @Override public boolean onStartJob(final JobParameters params) { switch (params.getJobId()) { case WRITE_FALLBACK_SETTINGS_FILES_JOB_ID: final List<String> settingsFiles = new ArrayList<>(); settingsFiles.add(params.getExtras().getString(TABLE_GLOBAL, "")); settingsFiles.add(params.getExtras().getString(TABLE_SYSTEM, "")); settingsFiles.add(params.getExtras().getString(TABLE_SECURE, "")); settingsFiles.add(params.getExtras().getString(TABLE_SSAID, "")); settingsFiles.add(params.getExtras().getString(TABLE_CONFIG, "")); SettingsProvider.writeFallBackSettingsFiles(settingsFiles); return true; default: return false; } } @Override public boolean onStopJob(JobParameters params) { return false; } }