Snap for 5115169 from 32cbfe9cf2 to qt-release

Change-Id: I22759da95a63d0bcbc2402d2b7b4678fd10f9714
This commit is contained in:
android-build-team Robot
2018-11-07 04:11:38 +00:00
32 changed files with 478 additions and 355 deletions

View File

@@ -1161,7 +1161,7 @@
<activity
android:name="Settings$LocationSettingsActivity"
android:label="@string/location_settings_title"
android:icon="@drawable/ic_settings_location"
android:icon="@drawable/ic_homepage_location"
android:configChanges="orientation|keyboardHidden|screenSize"
android:parentActivityName="Settings">
<intent-filter android:priority="1">
@@ -1180,7 +1180,7 @@
<activity
android:name="Settings$ScanningSettingsActivity"
android:label="@string/location_scanning_screen_title"
android:icon="@drawable/ic_settings_location"
android:icon="@drawable/ic_homepage_location"
android:configChanges="orientation|keyboardHidden|screenSize"
android:parentActivityName="Settings">
<intent-filter android:priority="1">

View File

@@ -1213,6 +1213,22 @@
column="5"/>
</issue>
<issue
id="HardCodedColor"
severity="Error"
message="Avoid using hardcoded color"
category="Correctness"
priority="4"
summary="Using hardcoded color"
explanation="Hardcoded color values are bad because theme changes cannot be uniformly applied.Instead use the theme specific colors such as `?android:attr/textColorPrimary` in attributes.&#xA;This ensures that a theme change from a light to a dark theme can be uniformlyapplied across the app."
errorLine1=" &lt;color name=&quot;homepage_location_background&quot;>#1A73E8&lt;/color>"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="res/values/colors.xml"
line="128"
column="5"/>
</issue>
<issue
id="HardCodedColor"
severity="Error"
@@ -1225,7 +1241,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="res/values/colors.xml"
line="132"
line="133"
column="5"/>
</issue>
@@ -1241,7 +1257,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="res/values/colors.xml"
line="133"
line="134"
column="5"/>
</issue>
@@ -1257,7 +1273,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="res/values/colors.xml"
line="134"
line="135"
column="5"/>
</issue>
@@ -1273,7 +1289,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="res/values/colors.xml"
line="135"
line="136"
column="5"/>
</issue>
@@ -1289,7 +1305,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="res/values/colors.xml"
line="136"
line="137"
column="5"/>
</issue>
@@ -1305,7 +1321,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="res/values/colors.xml"
line="137"
line="138"
column="5"/>
</issue>
@@ -1321,7 +1337,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="res/values/colors.xml"
line="140"
line="141"
column="5"/>
</issue>
@@ -1337,7 +1353,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="res/values/colors.xml"
line="141"
line="142"
column="5"/>
</issue>
@@ -1353,7 +1369,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="res/values/colors.xml"
line="142"
line="143"
column="5"/>
</issue>
@@ -1369,7 +1385,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="res/values/colors.xml"
line="143"
line="144"
column="5"/>
</issue>
@@ -1385,7 +1401,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="res/values/colors.xml"
line="144"
line="145"
column="5"/>
</issue>
@@ -1853,6 +1869,38 @@
column="17"/>
</issue>
<issue
id="HardCodedColor"
severity="Error"
message="Avoid using hardcoded color"
category="Correctness"
priority="4"
summary="Using hardcoded color"
explanation="Hardcoded color values are bad because theme changes cannot be uniformly applied.Instead use the theme specific colors such as `?android:attr/textColorPrimary` in attributes.&#xA;This ensures that a theme change from a light to a dark theme can be uniformlyapplied across the app."
errorLine1=" android:color=&quot;@color/homepage_location_background&quot; />"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="res/drawable/ic_homepage_location.xml"
line="23"
column="17"/>
</issue>
<issue
id="HardCodedColor"
severity="Error"
message="Avoid using hardcoded color"
category="Correctness"
priority="4"
summary="Using hardcoded color"
explanation="Hardcoded color values are bad because theme changes cannot be uniformly applied.Instead use the theme specific colors such as `?android:attr/textColorPrimary` in attributes.&#xA;This ensures that a theme change from a light to a dark theme can be uniformlyapplied across the app."
errorLine1=" android:color=&quot;@color/homepage_location_background&quot; />"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="res/drawable/ic_preference_location.xml"
line="23"
column="17"/>
</issue>
<issue
id="HardCodedColor"
severity="Error"

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z"
android:fillColor="#757575"/>
</vector>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2018 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.
-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="oval">
<solid
android:color="@color/homepage_location_background"/>
<size
android:width="@dimen/dashboard_tile_image_size"
android:height="@dimen/dashboard_tile_image_size"/>
</shape>
</item>
<item
android:width="@dimen/dashboard_tile_foreground_image_size"
android:height="@dimen/dashboard_tile_foreground_image_size"
android:start="@dimen/dashboard_tile_foreground_image_inset"
android:top="@dimen/dashboard_tile_foreground_image_inset"
android:drawable="@drawable/ic_settings_location"/>
</layer-list>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2018 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.
-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="oval">
<solid
android:color="@color/homepage_location_background"/>
<size
android:width="@android:dimen/app_icon_size"
android:height="@android:dimen/app_icon_size"/>
</shape>
</item>
<item
android:width="@dimen/dashboard_tile_foreground_image_size"
android:height="@dimen/dashboard_tile_foreground_image_size"
android:start="@dimen/preference_icon_foreground_image_inset"
android:top="@dimen/preference_icon_foreground_image_inset"
android:drawable="@drawable/ic_settings_location"/>
</layer-list>

View File

@@ -17,8 +17,7 @@
android:width="24.0dp"
android:height="24.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0"
android:tint="?android:attr/colorControlNormal">
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13C19,5.13 15.87,2 12,2zM7,9c0,-2.76 2.24,-5 5,-5s5,2.24 5,5c0,2.88 -2.88,7.19 -5,9.88C9.92,16.21 7,11.85 7,9z"/>

View File

@@ -125,6 +125,7 @@
<color name="homepage_system_background">#757575</color>
<color name="homepage_support_background">#26459C</color>
<color name="homepage_generic_icon_background">#1A73E8</color>
<color name="homepage_location_background">#2EC7DC</color>
<!-- End of dashboard/homepage icon background colors -->
<color name="glif_error_color">@*android:color/material_red_A700</color>

View File

@@ -88,6 +88,9 @@
<!-- Dashboard foreground image inset (from background edge to foreground edge) -->
<dimen name="dashboard_tile_foreground_image_inset">6dp</dimen>
<!-- Preference icon foreground image inset (from background edge to foreground edge) -->
<dimen name="preference_icon_foreground_image_inset">12dp</dimen>
<!-- SwitchBar sub settings margin start / end -->
<dimen name="switchbar_subsettings_margin_start">72dp</dimen>
<dimen name="switchbar_subsettings_margin_end">16dp</dimen>

View File

@@ -814,11 +814,13 @@
<string name="location_settings_title">Location</string>
<!-- Used in the location settings to control turning on/off the feature entirely -->
<string name="location_settings_master_switch_title">Use location</string>
<!-- Summary for Location settings, explaining a few important settings under it [CHAR LIMIT=NONE]-->
<string name="location_settings_summary">Scanning, location history</string>
<!-- Main Settings screen setting option title for the item to take you to the accounts screen [CHAR LIMIT=22] -->
<string name="account_settings_title">Accounts</string>
<!-- Main Settings screen setting option title for the item to take you to the security screen -->
<string name="security_settings_title">Security &amp; location</string>
<string name="security_settings_title">Security</string>
<!-- Security Settings screen setting option title for the item to take you to the encryption and credential screen -->
<string name="encryption_and_credential_settings_title">Encryption &amp; credentials</string>
<!-- Security Settings screen Encryption and crendential summary -->
@@ -8943,12 +8945,6 @@
<!-- Summary of payment screen [CHAR LIMIT=NONE] -->
<string name="payment_summary"><xliff:g id="app_name" example="Payment App">%1$s</xliff:g> is default</string>
<!-- Summary of location on screen [CHAR LIMIT=NONE] -->
<string name="location_on_summary">On</string>
<!-- Location off [CHAR LIMIT=NONE] -->
<string name="location_off_summary">Off</string>
<!-- Backup disabled summary [CHAR LIMIT=NONE] -->
<string name="backup_disabled">Back up disabled</string>

View File

@@ -95,12 +95,6 @@
android:key="security_settings_misc_category"
android:title="@string/security_passwords_title">
<Preference
android:key="location"
android:title="@string/location_settings_title"
android:summary="@string/summary_placeholder"
android:fragment="com.android.settings.location.LocationSettings" />
<SwitchPreference
android:key="show_password"
android:title="@string/show_password"

View File

@@ -26,7 +26,7 @@
android:title="@string/network_dashboard_title"
android:summary="@string/summary_placeholder"
android:icon="@drawable/ic_homepage_network"
android:order="-110"
android:order="-120"
android:fragment="com.android.settings.network.NetworkDashboardFragment"
settings:controller="com.android.settings.network.TopLevelNetworkEntryPreferenceController"/>
@@ -35,7 +35,7 @@
android:title="@string/connected_devices_dashboard_title"
android:summary="@string/summary_placeholder"
android:icon="@drawable/ic_homepage_connected_device"
android:order="-100"
android:order="-110"
android:fragment="com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment"
settings:controller="com.android.settings.connecteddevice.TopLevelConnectedDevicesPreferenceController"/>
@@ -44,7 +44,7 @@
android:title="@string/app_and_notification_dashboard_title"
android:summary="@string/app_and_notification_dashboard_summary"
android:icon="@drawable/ic_homepage_apps"
android:order="-90"
android:order="-100"
android:fragment="com.android.settings.applications.AppAndNotificationDashboardFragment"/>
<Preference
@@ -53,7 +53,7 @@
android:summary="@string/summary_placeholder"
android:icon="@drawable/ic_homepage_battery"
android:fragment="com.android.settings.fuelgauge.PowerUsageSummary"
android:order="-80"
android:order="-90"
settings:controller="com.android.settings.fuelgauge.TopLevelBatteryPreferenceController"/>
<Preference
@@ -61,7 +61,7 @@
android:title="@string/display_settings"
android:summary="@string/summary_placeholder"
android:icon="@drawable/ic_homepage_display"
android:order="-70"
android:order="-80"
android:fragment="com.android.settings.DisplaySettings"
settings:controller="com.android.settings.display.TopLevelDisplayPreferenceController"/>
@@ -70,7 +70,7 @@
android:title="@string/sound_settings"
android:summary="@string/sound_dashboard_summary"
android:icon="@drawable/ic_homepage_sound"
android:order="-60"
android:order="-70"
android:fragment="com.android.settings.notification.SoundSettings"/>
<Preference
@@ -78,10 +78,18 @@
android:title="@string/storage_settings"
android:summary="@string/summary_placeholder"
android:icon="@drawable/ic_homepage_storage"
android:order="-50"
android:order="-60"
android:fragment="com.android.settings.deviceinfo.StorageSettings"
settings:controller="com.android.settings.deviceinfo.TopLevelStoragePreferenceController"/>
<Preference
android:key="top_level_location"
android:title="@string/location_settings_title"
android:summary="@string/location_settings_summary"
android:icon="@drawable/ic_homepage_location"
android:order="-50"
android:fragment="com.android.settings.location.LocationSettings"/>
<Preference
android:key="top_level_security"
android:title="@string/security_settings_title"

View File

@@ -92,18 +92,18 @@ public class AccountDetailDashboardFragment extends DashboardFragment {
@VisibleForTesting
void finishIfAccountMissing() {
AccountManager accountManager = (AccountManager) getContext().getSystemService(
Context.ACCOUNT_SERVICE);
boolean accountExists = false;
for (Account account : accountManager.getAccountsByType(mAccount.type)) {
if (account.equals(mAccount)) {
accountExists = true;
break;
final Context context = getContext();
final UserManager um = context.getSystemService(UserManager.class);
final AccountManager accountManager = (AccountManager) context.getSystemService(
AccountManager.class);
for (UserHandle userHandle : um.getUserProfiles()) {
for (Account account : accountManager.getAccountsAsUser(userHandle.getIdentifier())) {
if (account.equals(mAccount)) {
return;
}
}
}
if (!accountExists) {
finish();
}
finish();
}
@Override
@@ -177,4 +177,4 @@ public class AccountDetailDashboardFragment extends DashboardFragment {
accountTypePreferenceLoader.updatePreferenceIntents(prefs, mAccountType, mAccount);
}
}
}
}

View File

@@ -63,7 +63,7 @@ public class BatteryAppListPreferenceController extends AbstractPreferenceContro
implements PreferenceControllerMixin, LifecycleObserver, OnPause, OnDestroy {
@VisibleForTesting
static final boolean USE_FAKE_DATA = false;
private static final int MAX_ITEMS_TO_LIST = USE_FAKE_DATA ? 30 : 10;
private static final int MAX_ITEMS_TO_LIST = USE_FAKE_DATA ? 30 : 20;
private static final int MIN_AVERAGE_POWER_THRESHOLD_MILLI_AMP = 10;
private static final int STATS_TYPE = BatteryStats.STATS_SINCE_CHARGED;

View File

@@ -16,6 +16,7 @@
package com.android.settings.homepage.contextualcards;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
@@ -30,7 +31,7 @@ import androidx.annotation.VisibleForTesting;
public class CardDatabaseHelper extends SQLiteOpenHelper {
private static final String TAG = "CardDatabaseHelper";
private static final String DATABASE_NAME = "homepage_cards.db";
private static final int DATABASE_VERSION = 4;
private static final int DATABASE_VERSION = 5;
public static final String CARD_TABLE = "cards";
@@ -119,6 +120,11 @@ public class CardDatabaseHelper extends SQLiteOpenHelper {
* Decide the card display full-length width or half-width in screen.
*/
String SUPPORT_HALF_WIDTH = "support_half_width";
/**
* Decide the card is dismissed or not.
*/
String CARD_DISMISSED = "card_dismissed";
}
private static final String CREATE_CARD_TABLE =
@@ -157,6 +163,8 @@ public class CardDatabaseHelper extends SQLiteOpenHelper {
CardColumns.EXPIRE_TIME_MS +
" INTEGER, " +
CardColumns.SUPPORT_HALF_WIDTH +
" INTEGER DEFAULT 0, " +
CardColumns.CARD_DISMISSED +
" INTEGER DEFAULT 0 " +
");";
@@ -190,9 +198,27 @@ public class CardDatabaseHelper extends SQLiteOpenHelper {
Cursor getContextualCards() {
final SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.query(CARD_TABLE, null /* columns */, null /* selection */,
final String selection = CardColumns.CARD_DISMISSED + "=0";
Cursor cursor = db.query(CARD_TABLE, null /* columns */, selection,
null /* selectionArgs */, null /* groupBy */, null /* having */,
null /* orderBy */);
return cursor;
}
/**
* Mark a specific ContextualCard with dismissal flag in the database to indicate that the
* card has been dismissed.
*
* @param cardName the card name of the ContextualCard which is dismissed by user.
* @return updated row number
*/
public int markContextualCardAsDismissed(String cardName) {
final SQLiteDatabase database = this.getWritableDatabase();
final ContentValues values = new ContentValues();
values.put(CardColumns.CARD_DISMISSED, 1);
final String selection = CardColumns.NAME + "=?";
final String[] selectionArgs = {cardName};
final int rowsUpdated = database.update(CARD_TABLE, values, selection, selectionArgs);
return rowsUpdated;
}
}

View File

@@ -84,7 +84,7 @@ public class ContextualCardManager implements ContextualCardLoader.CardContentLo
final CardContentLoaderCallbacks cardContentLoaderCallbacks =
new CardContentLoaderCallbacks(mContext);
cardContentLoaderCallbacks.setListener(this);
LoaderManager.getInstance(fragment).initLoader(CARD_CONTENT_LOADER_ID, null /* bundle */,
LoaderManager.getInstance(fragment).restartLoader(CARD_CONTENT_LOADER_ID, null /* bundle */,
cardContentLoaderCallbacks);
}

View File

@@ -43,6 +43,11 @@ public class ContextualCardsFragment extends InstrumentedFragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContextualCardManager = new ContextualCardManager(getContext(), getSettingsLifecycle());
}
@Override
public void onStart() {
super.onStart();
mContextualCardManager.loadContextualCards(this);
}

View File

@@ -1,111 +0,0 @@
/*
* 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.location;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.location.LocationManager;
import android.provider.Settings.Secure;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnPause;
import com.android.settingslib.core.lifecycle.events.OnResume;
public class LocationPreferenceController extends AbstractPreferenceController
implements PreferenceControllerMixin, LifecycleObserver, OnResume, OnPause {
private static final String KEY_LOCATION = "location";
private Context mContext;
private Preference mPreference;
@VisibleForTesting
BroadcastReceiver mLocationProvidersChangedReceiver;
public LocationPreferenceController(Context context, Lifecycle lifecycle) {
super(context);
mContext = context;
mLocationProvidersChangedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(LocationManager.PROVIDERS_CHANGED_ACTION)) {
updateSummary();
}
}
};
if (lifecycle != null) {
lifecycle.addObserver(this);
}
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPreference = screen.findPreference(KEY_LOCATION);
}
@Override
public void onResume() {
if (mLocationProvidersChangedReceiver != null) {
mContext.registerReceiver(mLocationProvidersChangedReceiver, new IntentFilter(
LocationManager.PROVIDERS_CHANGED_ACTION));
}
}
@Override
public void onPause() {
if (mLocationProvidersChangedReceiver != null) {
mContext.unregisterReceiver(mLocationProvidersChangedReceiver);
}
}
@Override
public void updateState(Preference preference) {
preference.setSummary(getLocationSummary(mContext));
}
@Override
public String getPreferenceKey() {
return KEY_LOCATION;
}
@Override
public boolean isAvailable() {
return true;
}
public void updateSummary() {
updateState(mPreference);
}
public static String getLocationSummary(Context context) {
int mode = Secure.getInt(context.getContentResolver(),
Secure.LOCATION_MODE, Secure.LOCATION_MODE_OFF);
if (mode != Secure.LOCATION_MODE_OFF) {
return context.getString(R.string.location_on_summary);
}
return context.getString(R.string.location_off_summary);
}
}

View File

@@ -133,34 +133,6 @@ public class LocationSettings extends DashboardFragment {
return controllers;
}
private static class SummaryProvider implements SummaryLoader.SummaryProvider {
private final Context mContext;
private final SummaryLoader mSummaryLoader;
public SummaryProvider(Context context, SummaryLoader summaryLoader) {
mContext = context;
mSummaryLoader = summaryLoader;
}
@Override
public void setListening(boolean listening) {
if (listening) {
mSummaryLoader.setSummary(
this, LocationPreferenceController.getLocationSummary(mContext));
}
}
}
public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY
= new SummaryLoader.SummaryProviderFactory() {
@Override
public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity,
SummaryLoader summaryLoader) {
return new SummaryProvider(activity, summaryLoader);
}
};
/**
* For Search.
*/

View File

@@ -29,7 +29,6 @@ import com.android.settings.biometrics.fingerprint.FingerprintProfileStatusPrefe
import com.android.settings.biometrics.fingerprint.FingerprintStatusPreferenceController;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.enterprise.EnterprisePrivacyPreferenceController;
import com.android.settings.location.LocationPreferenceController;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.security.trustagent.ManageTrustAgentsPreferenceController;
import com.android.settings.security.trustagent.TrustAgentListPreferenceController;
@@ -105,7 +104,6 @@ public class SecuritySettings extends DashboardFragment {
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
Lifecycle lifecycle, SecuritySettings host) {
final List<AbstractPreferenceController> controllers = new ArrayList<>();
controllers.add(new LocationPreferenceController(context, lifecycle));
controllers.add(new EnterprisePrivacyPreferenceController(context));
controllers.add(new ManageTrustAgentsPreferenceController(context));
controllers.add(new ScreenPinningPreferenceController(context));

View File

@@ -0,0 +1,28 @@
/*
* Copyright (C) 2018 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.slices;
/**
* Provide the copy ability for preference controller to copy the data to the clipboard.
*/
public interface CopyableSlice {
/**
* Copy the key slice information to the clipboard.
* It is highly recommended to show the toast to notify users when implemented this function.
*/
void copy();
}

View File

@@ -106,6 +106,12 @@ public class SettingsSliceProvider extends SliceProvider {
public static final String ACTION_SLIDER_CHANGED =
"com.android.settings.slice.action.SLIDER_CHANGED";
/**
* Action passed for copy data for the Copyable Slices.
*/
public static final String ACTION_COPY =
"com.android.settings.slice.action.COPY";
/**
* Intent Extra passed for the key identifying the Setting Slice.
*/

View File

@@ -24,6 +24,7 @@ import static com.android.settings.network.telephony.Enhanced4gLteSliceHelper
import static com.android.settings.notification.ZenModeSliceBuilder.ACTION_ZEN_MODE_SLICE_CHANGED;
import static com.android.settings.slices.SettingsSliceProvider.ACTION_SLIDER_CHANGED;
import static com.android.settings.slices.SettingsSliceProvider.ACTION_TOGGLE_CHANGED;
import static com.android.settings.slices.SettingsSliceProvider.ACTION_COPY;
import static com.android.settings.slices.SettingsSliceProvider.EXTRA_SLICE_KEY;
import static com.android.settings.slices.SettingsSliceProvider.EXTRA_SLICE_PLATFORM_DEFINED;
import static com.android.settings.wifi.calling.WifiCallingSliceHelper.ACTION_WIFI_CALLING_CHANGED;
@@ -115,6 +116,9 @@ public class SliceBroadcastReceiver extends BroadcastReceiver {
case ACTION_FLASHLIGHT_SLICE_CHANGED:
FlashlightSliceBuilder.handleUriChange(context, intent);
break;
case ACTION_COPY:
handleCopyAction(context, key, isPlatformSlice);
break;
}
}
@@ -184,6 +188,29 @@ public class SliceBroadcastReceiver extends BroadcastReceiver {
updateUri(context, key, isPlatformSlice);
}
private void handleCopyAction(Context context, String key, boolean isPlatformSlice) {
if (TextUtils.isEmpty(key)) {
throw new IllegalArgumentException("No key passed to Intent for controller");
}
final BasePreferenceController controller = getPreferenceController(context, key);
if (!(controller instanceof CopyableSlice)) {
throw new IllegalArgumentException(
"Copyable action passed for a non-copyable key:" + key);
}
if (!controller.isAvailable()) {
Log.w(TAG, "Can't update " + key + " since the setting is unavailable");
if (!controller.hasAsyncUpdate()) {
updateUri(context, key, isPlatformSlice);
}
return;
}
((CopyableSlice) controller).copy();
}
/**
* Log Slice value update events into MetricsFeatureProvider. The logging schema generally
* follows the pattern in SharedPreferenceLogger.

View File

@@ -93,6 +93,10 @@ public class SliceBuilderUtils {
return buildUnavailableSlice(context, sliceData);
}
if (controller instanceof CopyableSlice) {
return buildCopyableSlice(context, sliceData, controller);
}
switch (sliceData.getSliceType()) {
case SliceData.SliceType.INTENT:
return buildIntentSlice(context, sliceData, controller);
@@ -324,6 +328,28 @@ public class SliceBuilderUtils {
.build();
}
private static Slice buildCopyableSlice(Context context, SliceData sliceData,
BasePreferenceController controller) {
final SliceAction copyableAction = getCopyableAction(context, sliceData);
final PendingIntent contentIntent = getContentPendingIntent(context, sliceData);
final IconCompat icon = getSafeIcon(context, sliceData);
final SliceAction primaryAction = new SliceAction(contentIntent, icon,
sliceData.getTitle());
final CharSequence subtitleText = getSubtitleText(context, controller, sliceData);
@ColorInt final int color = Utils.getColorAccentDefaultColor(context);
final Set<String> keywords = buildSliceKeywords(sliceData);
return new ListBuilder(context, sliceData.getUri(), ListBuilder.INFINITY)
.setAccentColor(color)
.addRow(new RowBuilder()
.setTitle(sliceData.getTitle())
.setSubtitle(subtitleText)
.setPrimaryAction(primaryAction)
.addEndItem(copyableAction))
.setKeywords(keywords)
.build();
}
private static BasePreferenceController getPreferenceController(Context context,
String controllerClassName, String controllerKey) {
try {
@@ -346,6 +372,14 @@ public class SliceBuilderUtils {
return getActionIntent(context, SettingsSliceProvider.ACTION_SLIDER_CHANGED, sliceData);
}
private static SliceAction getCopyableAction(Context context, SliceData sliceData) {
final PendingIntent intent = getActionIntent(context,
SettingsSliceProvider.ACTION_COPY, sliceData);
final IconCompat icon = IconCompat.createWithResource(context,
R.drawable.ic_content_copy_grey600_24dp);
return new SliceAction(intent, icon, sliceData.getTitle());
}
private static boolean isValidSummary(Context context, CharSequence summary) {
if (summary == null || TextUtils.isEmpty(summary.toString().trim())) {
return false;

View File

@@ -450,7 +450,7 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen
private void addLocationAppRestrictionsPreference(AppRestrictionsHelper.SelectableAppInfo app,
AppRestrictionsPreference p) {
String packageName = app.packageName;
p.setIcon(R.drawable.ic_settings_location);
p.setIcon(R.drawable.ic_preference_location);
p.setKey(getKeyForPackage(packageName));
ArrayList<RestrictionEntry> restrictions = RestrictionUtils.getRestrictions(
getActivity(), mUser);

View File

@@ -35,8 +35,10 @@ import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import androidx.fragment.app.FragmentActivity;
import androidx.preference.Preference;
@@ -44,9 +46,12 @@ import androidx.preference.Preference;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.dashboard.DashboardFeatureProviderImpl;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.shadow.ShadowAccountManager;
import com.android.settings.testutils.shadow.ShadowUserManager;
import com.android.settingslib.drawer.CategoryKey;
import com.android.settingslib.drawer.Tile;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -54,10 +59,11 @@ import org.robolectric.Robolectric;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.Shadows;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowAccountManager;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.ReflectionHelpers;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(shadows = {ShadowAccountManager.class, ShadowUserManager.class})
public class AccountDetailDashboardFragmentTest {
private static final String METADATA_CATEGORY = "com.android.settings.category";
@@ -86,6 +92,11 @@ public class AccountDetailDashboardFragmentTest {
when(mFragment.getContext()).thenReturn(mContext);
}
@After
public void tearDown() {
ShadowAccountManager.reset();
}
@Test
public void testCategory_isAccountDetail() {
assertThat(new AccountDetailDashboardFragment().getCategoryKey())
@@ -152,17 +163,45 @@ public class AccountDetailDashboardFragmentTest {
}
@Test
@Config(shadows = {ShadowAccountManager.class})
public void onResume_accountMissing_shouldFinish() {
ShadowUserManager userManager = Shadow.extract(
mContext.getSystemService(UserManager.class));
ShadowAccountManager acctMgr = Shadow.extract(
mContext.getSystemService(AccountManager.class));
userManager.addProfile(new UserInfo(1, null, 0));
acctMgr.addAccountForUser(1, new Account("test@test.com", "com.test"));
mFragment.finishIfAccountMissing();
verify(mFragment).finish();
}
@Test
@Config(shadows = {ShadowAccountManager.class})
public void onResume_accountPresent_shouldNotFinish() {
AccountManager mgr = mContext.getSystemService(AccountManager.class);
Shadows.shadowOf(mgr).addAccount(mFragment.mAccount);
public void onResume_accountPresentOneProfile_shouldNotFinish() {
ShadowUserManager userManager = Shadow.extract(
mContext.getSystemService(UserManager.class));
ShadowAccountManager acctMgr = Shadow.extract(
mContext.getSystemService(AccountManager.class));
userManager.addProfile(new UserInfo(1, null, 0));
acctMgr.addAccountForUser(1, mFragment.mAccount);
mFragment.finishIfAccountMissing();
verify(mFragment, never()).finish();
}
@Test
public void onResume_accountPresentTwoProfiles_shouldNotFinish() {
ShadowUserManager userManager = Shadow.extract(
mContext.getSystemService(UserManager.class));
ShadowAccountManager acctMgr = Shadow.extract(
mContext.getSystemService(AccountManager.class));
userManager.addProfile(new UserInfo(1, null, 0));
userManager.addProfile(new UserInfo(2, null, 0));
acctMgr.addAccountForUser(1, new Account("test@test.com", "com.test"));
acctMgr.addAccountForUser(2, mFragment.mAccount);
mFragment.finishIfAccountMissing();
verify(mFragment, never()).finish();
}

View File

@@ -76,7 +76,7 @@ public class ChooseAccountPreferenceControllerTest {
@After
public void tearDown() {
ShadowContentResolver.reset();
ShadowAccountManager.resetAuthenticator();
ShadowAccountManager.reset();
ShadowRestrictedLockUtilsInternal.clearDisabledTypes();
}

View File

@@ -74,6 +74,7 @@ public class CardDatabaseHelperTest {
CardDatabaseHelper.CardColumns.CARD_ACTION,
CardDatabaseHelper.CardColumns.EXPIRE_TIME_MS,
CardDatabaseHelper.CardColumns.SUPPORT_HALF_WIDTH,
CardDatabaseHelper.CardColumns.CARD_DISMISSED,
};
assertThat(columnNames).isEqualTo(expectedNames);

View File

@@ -1,153 +0,0 @@
/*
* 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.location;
import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE;
import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.location.LocationManager;
import android.provider.Settings.Secure;
import androidx.lifecycle.LifecycleOwner;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settingslib.core.lifecycle.Lifecycle;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@RunWith(SettingsRobolectricTestRunner.class)
public class LocationPreferenceControllerTest {
@Mock
private Preference mPreference;
@Mock
private PreferenceScreen mScreen;
private LifecycleOwner mLifecycleOwner;
private Lifecycle mLifecycle;
private LocationPreferenceController mController;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Context mContext;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mLifecycleOwner = () -> mLifecycle;
mLifecycle = new Lifecycle(mLifecycleOwner);
mController = new LocationPreferenceController(mContext, mLifecycle);
when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
}
@Test
public void isAvailable_shouldReturnTrue() {
assertThat(mController.isAvailable()).isTrue();
}
@Test
public void updateState_shouldSetSummary() {
mController.updateState(mPreference);
verify(mPreference).setSummary(nullable(String.class));
}
@Test
public void updateSummary_shouldSetSummary() {
mController.displayPreference(mScreen);
mController.updateSummary();
verify(mPreference).setSummary(nullable(String.class));
}
@Test
public void getLocationSummary_locationOff_shouldSetSummaryOff() {
final ContentResolver contentResolver = mContext.getContentResolver();
Secure.putInt(contentResolver, Secure.LOCATION_MODE, Secure.LOCATION_MODE_OFF);
final String locationSummary = mController.getLocationSummary(mContext);
assertThat(locationSummary).isEqualTo(mContext.getString(R.string.location_off_summary));
}
@Test
public void getLocationSummary_sensorsOnly_shouldSetSummaryOn() {
final ContentResolver contentResolver = mContext.getContentResolver();
Secure.putInt(contentResolver, Secure.LOCATION_MODE, Secure.LOCATION_MODE_SENSORS_ONLY);
final String locationSummary = mController.getLocationSummary(mContext);
assertThat(locationSummary).isEqualTo(mContext.getString(R.string.location_on_summary));
}
@Test
public void getLocationSummary_highAccuracy_shouldSetSummaryOn() {
final ContentResolver contentResolver = mContext.getContentResolver();
Secure.putInt(contentResolver, Secure.LOCATION_MODE, Secure.LOCATION_MODE_HIGH_ACCURACY);
final String locationSummary = mController.getLocationSummary(mContext);
assertThat(locationSummary).isEqualTo(mContext.getString(R.string.location_on_summary));
}
@Test
public void getLocationSummary_batterySaving_shouldSetSummaryOn() {
final ContentResolver contentResolver = mContext.getContentResolver();
Secure.putInt(contentResolver, Secure.LOCATION_MODE, Secure.LOCATION_MODE_BATTERY_SAVING);
final String locationSummary = mController.getLocationSummary(mContext);
assertThat(locationSummary).isEqualTo(mContext.getString(R.string.location_on_summary));
}
@Test
public void onResume_shouldRegisterObserver() {
mLifecycle.handleLifecycleEvent(ON_RESUME);
verify(mContext).registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class));
}
@Test
public void onPause_shouldUnregisterObserver() {
mLifecycle.handleLifecycleEvent(ON_RESUME);
mLifecycle.handleLifecycleEvent(ON_PAUSE);
verify(mContext).unregisterReceiver(any(BroadcastReceiver.class));
}
@Test
public void locationProvidersChangedReceiver_updatesPreferenceSummary() {
mController.displayPreference(mScreen);
mController.onResume();
mController.mLocationProvidersChangedReceiver
.onReceive(mContext, new Intent(LocationManager.PROVIDERS_CHANGED_ACTION));
verify(mPreference).setSummary(any());
}
}

View File

@@ -41,6 +41,7 @@ import androidx.slice.widget.SliceLiveData;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.testutils.FakeCopyableController;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.FakeSliderController;
import com.android.settings.testutils.FakeToggleController;
@@ -67,6 +68,7 @@ public class SliceBuilderUtilsTest {
private final Uri URI = Uri.parse("content://com.android.settings.slices/test");
private final Class TOGGLE_CONTROLLER = FakeToggleController.class;
private final Class SLIDER_CONTROLLER = FakeSliderController.class;
private final Class COPYABLE_CONTROLLER = FakeCopyableController.class;
private final Class CONTEXT_CONTROLLER = FakeContextOnlyPreferenceController.class;
private final boolean IS_DYNAMIC_SUMMARY_ALLOWED = false;
@@ -116,7 +118,6 @@ public class SliceBuilderUtilsTest {
public void buildSliderSlice_returnsMatchingSlice() {
final SliceData data = getDummyData(SLIDER_CONTROLLER, SliceData.SliceType.SLIDER);
final Slice slice = SliceBuilderUtils.buildSlice(mContext, data);
verify(mFeatureFactory.metricsFeatureProvider)
.action(eq(mContext), eq(MetricsEvent.ACTION_SETTINGS_SLICE_REQUESTED),
@@ -130,6 +131,23 @@ public class SliceBuilderUtilsTest {
SliceTester.testSettingsSliderSlice(mContext, slice, data);
}
@Test
public void buildCopyableSlice_returnsMatchingSlice() {
final SliceData dummyData = getDummyData(COPYABLE_CONTROLLER, -1);
final Slice slice = SliceBuilderUtils.buildSlice(mContext, dummyData);
verify(mFeatureFactory.metricsFeatureProvider)
.action(eq(mContext), eq(MetricsEvent.ACTION_SETTINGS_SLICE_REQUESTED),
mLoggingArgumentCatpor.capture());
final Pair<Integer, Object> capturedLoggingPair = mLoggingArgumentCatpor.getValue();
assertThat(capturedLoggingPair.first)
.isEqualTo(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME);
assertThat(capturedLoggingPair.second)
.isEqualTo(dummyData.getKey());
SliceTester.testSettingsCopyableSlice(mContext, slice, dummyData);
}
@Test
public void testUriBuilder_oemAuthority_intentPath_returnsValidSliceUri() {
final Uri expectedUri = new Uri.Builder()

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2018 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.testutils;
import android.content.Context;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.slices.CopyableSlice;
public class FakeCopyableController extends BasePreferenceController implements
CopyableSlice {
public FakeCopyableController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public boolean isSliceable() {
return true;
}
@Override
public void copy() {
}
}

View File

@@ -168,6 +168,43 @@ public class SliceTester {
assertKeywords(metadata, sliceData);
}
/**
* Test the copyable slice, including:
* - No intent
* - Correct title
* - Correct intent
* - Correct keywords
* - TTL
* - Color
*/
public static void testSettingsCopyableSlice(Context context, Slice slice,
SliceData sliceData) {
final SliceMetadata metadata = SliceMetadata.from(context, slice);
final SliceItem colorItem = SliceQuery.findSubtype(slice, FORMAT_INT, SUBTYPE_COLOR);
final int color = colorItem.getInt();
assertThat(color).isEqualTo(Utils.getColorAccentDefaultColor(context));
final SliceAction primaryAction = metadata.getPrimaryAction();
final IconCompat expectedIcon = IconCompat.createWithResource(context,
sliceData.getIconResource());
assertThat(expectedIcon.toString()).isEqualTo(primaryAction.getIcon().toString());
final long sliceTTL = metadata.getExpiry();
assertThat(sliceTTL).isEqualTo(ListBuilder.INFINITY);
// Check primary intent
final PendingIntent primaryPendingIntent = primaryAction.getAction();
assertThat(primaryPendingIntent).isEqualTo(
SliceBuilderUtils.getContentPendingIntent(context, sliceData));
final List<SliceItem> sliceItems = slice.getItems();
assertTitle(sliceItems, sliceData.getTitle());
assertKeywords(metadata, sliceData);
}
/**
* Test the contents of an unavailable slice, including:
* - No toggles
@@ -229,4 +266,4 @@ public class SliceTester {
expectedKeywords.add(data.getScreenTitle().toString());
assertThat(keywords).containsExactlyElementsIn(expectedKeywords);
}
}
}

View File

@@ -16,19 +16,24 @@
package com.android.settings.testutils.shadow;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AuthenticatorDescription;
import android.annotation.NonNull;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Implements(AccountManager.class)
public class ShadowAccountManager{
private static final Map<String, AuthenticatorDescription> sAuthenticators = new HashMap<>();
private static final Map<Integer, List<Account>> sAccountsByUserId = new HashMap<>();
@Implementation
public AuthenticatorDescription[] getAuthenticatorTypesAsUser(int userId) {
@@ -39,7 +44,24 @@ public class ShadowAccountManager{
sAuthenticators.put(authenticator.type, authenticator);
}
public static void resetAuthenticator() {
public static void reset() {
sAuthenticators.clear();
sAccountsByUserId.clear();
}
@Implementation @NonNull
public Account[] getAccountsAsUser(int userId) {
if (sAccountsByUserId.containsKey(userId)) {
return sAccountsByUserId.get(userId).toArray(new Account[0]);
} else {
return new Account[0];
}
}
public static void addAccountForUser(int userId, Account account) {
if (!sAccountsByUserId.containsKey(userId)) {
sAccountsByUserId.put(userId, new ArrayList<>());
}
sAccountsByUserId.get(userId).add(account);
}
}