diff --git a/src/com/android/settings/IccLockSettings.java b/src/com/android/settings/IccLockSettings.java index 2cb49041136..1de7a10fe27 100644 --- a/src/com/android/settings/IccLockSettings.java +++ b/src/com/android/settings/IccLockSettings.java @@ -20,7 +20,9 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.res.Configuration; import android.content.res.Resources; +import android.graphics.PixelFormat; import android.os.AsyncResult; import android.os.Bundle; import android.os.Handler; @@ -30,9 +32,11 @@ import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; +import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.WindowManager; import android.widget.EditText; import android.widget.ListView; import android.widget.TabHost; @@ -40,6 +44,7 @@ import android.widget.TabHost.OnTabChangeListener; import android.widget.TabHost.TabContentFactory; import android.widget.TabHost.TabSpec; import android.widget.TabWidget; +import android.widget.TextView; import android.widget.Toast; import androidx.preference.Preference; @@ -118,6 +123,9 @@ public class IccLockSettings extends SettingsPreferenceFragment private static final int MSG_CHANGE_ICC_PIN_COMPLETE = 101; private static final int MSG_SIM_STATE_CHANGED = 102; + // @see android.widget.Toast$TN + private static final long LONG_DURATION_TIMEOUT = 7000; + // For replies from IccCard interface private Handler mHandler = new Handler() { public void handleMessage(Message msg) { @@ -461,8 +469,7 @@ public class IccLockSettings extends SettingsPreferenceFragment if (exception instanceof CommandException) { CommandException.Error err = ((CommandException)(exception)).getCommandError(); if (err == CommandException.Error.PASSWORD_INCORRECT) { - Toast.makeText(getContext(), getPinPasswordErrorMessage(attemptsRemaining), - Toast.LENGTH_LONG).show(); + createCustomTextToast(getPinPasswordErrorMessage(attemptsRemaining)); } else { if (mToState) { Toast.makeText(getContext(), mRes.getString @@ -478,11 +485,56 @@ public class IccLockSettings extends SettingsPreferenceFragment resetDialogState(); } + private void createCustomTextToast(CharSequence errorMessage) { + // Cannot overlay Toast on PUK unlock screen. + // The window type of Toast is set by NotificationManagerService. + // It can't be overwritten by LayoutParams.type. + // Ovarlay a custom window with LayoutParams (TYPE_STATUS_BAR_PANEL) on PUK unlock screen. + View v = ((LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE)) + .inflate(com.android.internal.R.layout.transient_notification, null); + TextView tv = (TextView) v.findViewById(com.android.internal.R.id.message); + tv.setText(errorMessage); + + final WindowManager.LayoutParams params = new WindowManager.LayoutParams(); + final Configuration config = v.getContext().getResources().getConfiguration(); + final int gravity = Gravity.getAbsoluteGravity( + getContext().getResources().getInteger( + com.android.internal.R.integer.config_toastDefaultGravity), + config.getLayoutDirection()); + params.gravity = gravity; + if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) { + params.horizontalWeight = 1.0f; + } + if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) { + params.verticalWeight = 1.0f; + } + params.y = getContext().getResources().getDimensionPixelSize( + com.android.internal.R.dimen.toast_y_offset); + + params.height = WindowManager.LayoutParams.WRAP_CONTENT; + params.width = WindowManager.LayoutParams.WRAP_CONTENT; + params.format = PixelFormat.TRANSLUCENT; + params.windowAnimations = com.android.internal.R.style.Animation_Toast; + params.type = WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL; + params.setTitle(errorMessage); + params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; + + WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE); + wm.addView(v, params); + + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + wm.removeViewImmediate(v); + } + }, LONG_DURATION_TIMEOUT); + } + private void iccPinChanged(boolean success, int attemptsRemaining) { if (!success) { - Toast.makeText(getContext(), getPinPasswordErrorMessage(attemptsRemaining), - Toast.LENGTH_LONG) - .show(); + createCustomTextToast(getPinPasswordErrorMessage(attemptsRemaining)); } else { Toast.makeText(getContext(), mRes.getString(R.string.sim_change_succeeded), Toast.LENGTH_SHORT) diff --git a/src/com/android/settings/fuelgauge/BatteryInfo.java b/src/com/android/settings/fuelgauge/BatteryInfo.java index 06cdad6a68e..1f11f5a0cf6 100644 --- a/src/com/android/settings/fuelgauge/BatteryInfo.java +++ b/src/com/android/settings/fuelgauge/BatteryInfo.java @@ -148,49 +148,7 @@ public class BatteryInfo { new AsyncTask() { @Override protected BatteryInfo doInBackground(Void... params) { - final BatteryStats stats; - final long batteryStatsTime = System.currentTimeMillis(); - if (statsHelper == null) { - final BatteryStatsHelper localStatsHelper = new BatteryStatsHelper(context, - true); - localStatsHelper.create((Bundle) null); - stats = localStatsHelper.getStats(); - } else { - stats = statsHelper.getStats(); - } - BatteryUtils.logRuntime(LOG_TAG, "time for getStats", batteryStatsTime); - - final long startTime = System.currentTimeMillis(); - PowerUsageFeatureProvider provider = - FeatureFactory.getFactory(context).getPowerUsageFeatureProvider(context); - final long elapsedRealtimeUs = - PowerUtil.convertMsToUs(SystemClock.elapsedRealtime()); - - Intent batteryBroadcast = context.registerReceiver(null, - new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); - // 0 means we are discharging, anything else means charging - boolean discharging = - batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) == 0; - - if (discharging && provider != null - && provider.isEnhancedBatteryPredictionEnabled(context)) { - Estimate estimate = provider.getEnhancedBatteryPrediction(context); - if (estimate != null) { - BatteryUtils - .logRuntime(LOG_TAG, "time for enhanced BatteryInfo", startTime); - return BatteryInfo.getBatteryInfo(context, batteryBroadcast, stats, - estimate, elapsedRealtimeUs, shortString); - } - } - long prediction = discharging - ? stats.computeBatteryTimeRemaining(elapsedRealtimeUs) : 0; - Estimate estimate = new Estimate( - PowerUtil.convertUsToMs(prediction), - false, /* isBasedOnUsage */ - Estimate.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN); - BatteryUtils.logRuntime(LOG_TAG, "time for regular BatteryInfo", startTime); - return BatteryInfo.getBatteryInfo(context, batteryBroadcast, stats, - estimate, elapsedRealtimeUs, shortString); + return getBatteryInfo(context, statsHelper, shortString); } @Override @@ -202,6 +160,53 @@ public class BatteryInfo { }.execute(); } + public static BatteryInfo getBatteryInfo(final Context context, + final BatteryStatsHelper statsHelper, boolean shortString) { + final BatteryStats stats; + final long batteryStatsTime = System.currentTimeMillis(); + if (statsHelper == null) { + final BatteryStatsHelper localStatsHelper = new BatteryStatsHelper(context, + true); + localStatsHelper.create((Bundle) null); + stats = localStatsHelper.getStats(); + } else { + stats = statsHelper.getStats(); + } + BatteryUtils.logRuntime(LOG_TAG, "time for getStats", batteryStatsTime); + + final long startTime = System.currentTimeMillis(); + PowerUsageFeatureProvider provider = + FeatureFactory.getFactory(context).getPowerUsageFeatureProvider(context); + final long elapsedRealtimeUs = + PowerUtil.convertMsToUs(SystemClock.elapsedRealtime()); + + final Intent batteryBroadcast = context.registerReceiver(null, + new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); + // 0 means we are discharging, anything else means charging + final boolean discharging = + batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) == 0; + + if (discharging && provider != null + && provider.isEnhancedBatteryPredictionEnabled(context)) { + Estimate estimate = provider.getEnhancedBatteryPrediction(context); + if (estimate != null) { + BatteryUtils + .logRuntime(LOG_TAG, "time for enhanced BatteryInfo", startTime); + return BatteryInfo.getBatteryInfo(context, batteryBroadcast, stats, + estimate, elapsedRealtimeUs, shortString); + } + } + final long prediction = discharging + ? stats.computeBatteryTimeRemaining(elapsedRealtimeUs) : 0; + final Estimate estimate = new Estimate( + PowerUtil.convertUsToMs(prediction), + false, /* isBasedOnUsage */ + Estimate.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN); + BatteryUtils.logRuntime(LOG_TAG, "time for regular BatteryInfo", startTime); + return BatteryInfo.getBatteryInfo(context, batteryBroadcast, stats, + estimate, elapsedRealtimeUs, shortString); + } + @WorkerThread public static BatteryInfo getBatteryInfoOld(Context context, Intent batteryBroadcast, BatteryStats stats, long elapsedRealtimeUs, boolean shortString) { diff --git a/src/com/android/settings/homepage/CardContentLoader.java b/src/com/android/settings/homepage/CardContentLoader.java index 401e53cbbbb..7168b676690 100644 --- a/src/com/android/settings/homepage/CardContentLoader.java +++ b/src/com/android/settings/homepage/CardContentLoader.java @@ -32,6 +32,7 @@ import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import androidx.slice.Slice; +import com.android.settings.homepage.deviceinfo.BatterySlice; import com.android.settings.homepage.deviceinfo.DataUsageSlice; import com.android.settings.homepage.deviceinfo.DeviceInfoSlice; import com.android.settingslib.utils.AsyncLoaderCompat; @@ -101,17 +102,15 @@ public class CardContentLoader extends AsyncLoaderCompat> { .setCardType(ContextualCard.CardType.SLICE) .setIsHalfWidth(false) .build()); - //TODO(b/115971399): Will change following values of SliceUri and Name - // after landing these slice cards. -// add(new ContextualCard.Builder() -// .setSliceUri("content://com.android.settings.slices/battery_card") -// .setName(packageName + "/" + "battery_card") -// .setPackageName(packageName) -// .setRankingScore(rankingScore) -// .setAppVersion(appVersionCode) -// .setCardType(ContextualCard.CardType.SLICE) -// .setIsHalfWidth(true) -// .build()); + add(new ContextualCard.Builder() + .setSliceUri(BatterySlice.BATTERY_CARD_URI) + .setName(BatterySlice.PATH_BATTERY_INFO) + .setPackageName(packageName) + .setRankingScore(rankingScore) + .setAppVersion(appVersionCode) + .setCardType(ContextualCard.CardType.SLICE) + .setIsHalfWidth(false) + .build()); add(new ContextualCard.Builder() .setSliceUri(DeviceInfoSlice.DEVICE_INFO_CARD_URI) .setName(DeviceInfoSlice.PATH_DEVICE_INFO) @@ -154,6 +153,7 @@ public class CardContentLoader extends AsyncLoaderCompat> { final Slice slice = Slice.bindSlice(mContext, uri, SUPPORTED_SPECS); if (slice == null || slice.hasHint(HINT_ERROR)) { + Log.w(TAG, "Failed to bind slice, not eligible for display " + uri); return false; } diff --git a/src/com/android/settings/homepage/SettingsHomepageActivity.java b/src/com/android/settings/homepage/SettingsHomepageActivity.java index c6040f608c4..da7e7f2ee16 100644 --- a/src/com/android/settings/homepage/SettingsHomepageActivity.java +++ b/src/com/android/settings/homepage/SettingsHomepageActivity.java @@ -65,7 +65,13 @@ public class SettingsHomepageActivity extends SettingsBaseActivity { private void showFragment(Fragment fragment, int id, String tag) { final FragmentManager fragmentManager = getSupportFragmentManager(); final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); - fragmentTransaction.add(id, fragment, tag); + final Fragment showFragment = fragmentManager.findFragmentById(id); + + if (showFragment == null) { + fragmentTransaction.add(id, fragment, tag); + } else { + fragmentTransaction.show(showFragment); + } fragmentTransaction.commit(); } } \ No newline at end of file diff --git a/src/com/android/settings/homepage/contextualcards/SettingsContextualCardProvider.java b/src/com/android/settings/homepage/contextualcards/SettingsContextualCardProvider.java index 26f86b9da5e..0d72cebd3b3 100644 --- a/src/com/android/settings/homepage/contextualcards/SettingsContextualCardProvider.java +++ b/src/com/android/settings/homepage/contextualcards/SettingsContextualCardProvider.java @@ -20,6 +20,7 @@ import static android.provider.SettingsSlicesContract.KEY_WIFI; import android.annotation.Nullable; +import com.android.settings.homepage.deviceinfo.BatterySlice; import com.android.settings.homepage.deviceinfo.DataUsageSlice; import com.android.settings.homepage.deviceinfo.DeviceInfoSlice; import com.android.settings.homepage.deviceinfo.StorageSlice; @@ -63,12 +64,18 @@ public class SettingsContextualCardProvider extends ContextualCardProvider { .setSliceUri(EmergencyInfoSlice.EMERGENCY_INFO_CARD_URI.toString()) .setCardName(EmergencyInfoSlice.PATH_EMERGENCY_INFO_CARD) .build(); + final ContextualCard batteryInfoCard = + ContextualCard.newBuilder() + .setSliceUri(BatterySlice.BATTERY_CARD_URI.toSafeString()) + .setCardName(BatterySlice.PATH_BATTERY_INFO) + .build(); final ContextualCardList cards = ContextualCardList.newBuilder() .addCard(wifiCard) .addCard(dataUsageCard) .addCard(deviceInfoCard) .addCard(storageInfoCard) .addCard(emergencyInfoCard) + .addCard(batteryInfoCard) .build(); return cards; diff --git a/src/com/android/settings/homepage/deviceinfo/BatterySlice.java b/src/com/android/settings/homepage/deviceinfo/BatterySlice.java new file mode 100644 index 00000000000..1090b40fe8b --- /dev/null +++ b/src/com/android/settings/homepage/deviceinfo/BatterySlice.java @@ -0,0 +1,156 @@ +/* + * 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.homepage.deviceinfo; + +import android.app.PendingIntent; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.Uri; +import android.os.PowerManager; + +import androidx.annotation.VisibleForTesting; +import androidx.core.graphics.drawable.IconCompat; +import androidx.slice.Slice; +import androidx.slice.builders.ListBuilder; +import androidx.slice.builders.SliceAction; + +import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.R; +import com.android.settings.SubSettings; +import com.android.settings.Utils; +import com.android.settings.fuelgauge.BatteryInfo; +import com.android.settings.fuelgauge.PowerUsageSummary; +import com.android.settings.slices.CustomSliceable; +import com.android.settings.slices.SettingsSliceProvider; +import com.android.settings.slices.SliceBuilderUtils; + +/** + * Utility class to build a Battery Slice, and handle all associated actions. + */ +public class BatterySlice implements CustomSliceable { + private static final String TAG = "BatterySlice"; + + /** + * The path denotes the unique name of battery slice. + */ + public static final String PATH_BATTERY_INFO = "battery_card"; + + /** + * Backing Uri for the Battery Slice. + */ + public static final Uri BATTERY_CARD_URI = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSliceProvider.SLICE_AUTHORITY) + .appendPath(PATH_BATTERY_INFO) + .build(); + + private final Context mContext; + + private BatteryInfo mBatteryInfo; + private boolean mIsBatteryInfoLoading; + + public BatterySlice(Context context) { + mContext = context; + } + + /** + * Return a {@link BatterySlice} bound to {@link #BATTERY_CARD_URI} + */ + @Override + public Slice getSlice() { + if (mBatteryInfo == null) { + mIsBatteryInfoLoading = true; + loadBatteryInfo(); + } + final IconCompat icon = IconCompat.createWithResource(mContext, + R.drawable.ic_settings_battery); + final CharSequence title = mContext.getText(R.string.power_usage_summary_title); + final SliceAction primarySliceAction = new SliceAction(getPrimaryAction(), icon, title); + final Slice slice = new ListBuilder(mContext, BATTERY_CARD_URI, ListBuilder.INFINITY) + .setAccentColor(Utils.getColorAccentDefaultColor(mContext)) + .setHeader(new ListBuilder.HeaderBuilder().setTitle(title)) + .addRow(new ListBuilder.RowBuilder() + .setTitle(getBatteryPercentString(), mIsBatteryInfoLoading) + .setSubtitle(getSummary(), mIsBatteryInfoLoading) + .setPrimaryAction(primarySliceAction)) + .build(); + mBatteryInfo = null; + mIsBatteryInfoLoading = false; + return slice; + } + + @Override + public Uri getUri() { + return BATTERY_CARD_URI; + } + + @Override + public void onNotifyChange(Intent intent) { + + } + + @Override + public Intent getIntent() { + final String screenTitle = mContext.getText(R.string.power_usage_summary_title).toString(); + final Uri contentUri = new Uri.Builder().appendPath(PATH_BATTERY_INFO).build(); + return SliceBuilderUtils.buildSearchResultPageIntent(mContext, + PowerUsageSummary.class.getName(), PATH_BATTERY_INFO, screenTitle, + MetricsProto.MetricsEvent.SLICE) + .setClassName(mContext.getPackageName(), SubSettings.class.getName()) + .setData(contentUri); + } + + @Override + public IntentFilter getIntentFilter() { + final IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED); + intentFilter.addAction(Intent.ACTION_POWER_CONNECTED); + intentFilter.addAction(Intent.ACTION_POWER_DISCONNECTED); + intentFilter.addAction(Intent.ACTION_BATTERY_LEVEL_CHANGED); + return intentFilter; + } + + @VisibleForTesting + void loadBatteryInfo() { + BatteryInfo.getBatteryInfo(mContext, info -> { + mBatteryInfo = info; + mContext.getContentResolver().notifyChange(getUri(), null); + }, true); + } + + @VisibleForTesting + CharSequence getBatteryPercentString() { + return mBatteryInfo == null ? null : mBatteryInfo.batteryPercentString; + } + + @VisibleForTesting + CharSequence getSummary() { + if (mBatteryInfo == null) { + return null; + } + return mBatteryInfo.remainingLabel == null ? mBatteryInfo.statusLabel + : mBatteryInfo.remainingLabel; + } + + private PendingIntent getPrimaryAction() { + final Intent intent = getIntent(); + return PendingIntent.getActivity(mContext, 0 /* requestCode */, + intent, 0 /* flags */); + } +} \ No newline at end of file diff --git a/src/com/android/settings/slices/CustomSliceManager.java b/src/com/android/settings/slices/CustomSliceManager.java index 8fa2fb6fdba..a207fccfcb6 100644 --- a/src/com/android/settings/slices/CustomSliceManager.java +++ b/src/com/android/settings/slices/CustomSliceManager.java @@ -20,12 +20,14 @@ import android.content.Context; import android.net.Uri; import android.util.ArrayMap; +import com.android.settings.homepage.deviceinfo.BatterySlice; import com.android.settings.homepage.deviceinfo.DataUsageSlice; import com.android.settings.homepage.deviceinfo.DeviceInfoSlice; import com.android.settings.homepage.deviceinfo.StorageSlice; import com.android.settings.wifi.WifiSlice; import java.util.Map; +import java.util.WeakHashMap; /** * Manages custom {@link androidx.slice.Slice Slices}, which are all Slices not backed by @@ -39,10 +41,12 @@ public class CustomSliceManager { protected final Map> mUriMap; private final Context mContext; + private final Map mSliceableCache; public CustomSliceManager(Context context) { mContext = context.getApplicationContext(); mUriMap = new ArrayMap<>(); + mSliceableCache = new WeakHashMap<>(); addSlices(); } @@ -53,13 +57,18 @@ public class CustomSliceManager { * the only thing that should be needed to create the object. */ public CustomSliceable getSliceableFromUri(Uri uri) { - final Class clazz = mUriMap.get(uri); + if (mSliceableCache.containsKey(uri)) { + return mSliceableCache.get(uri); + } + final Class clazz = mUriMap.get(uri); if (clazz == null) { throw new IllegalArgumentException("No Slice found for uri: " + uri); } - return CustomSliceable.createInstance(mContext, clazz); + final CustomSliceable sliceable = CustomSliceable.createInstance(mContext, clazz); + mSliceableCache.put(uri, sliceable); + return sliceable; } /** @@ -93,5 +102,6 @@ public class CustomSliceManager { mUriMap.put(DataUsageSlice.DATA_USAGE_CARD_URI, DataUsageSlice.class); mUriMap.put(DeviceInfoSlice.DEVICE_INFO_CARD_URI, DeviceInfoSlice.class); mUriMap.put(StorageSlice.STORAGE_CARD_URI, StorageSlice.class); + mUriMap.put(BatterySlice.BATTERY_CARD_URI, BatterySlice.class); } -} \ No newline at end of file +} diff --git a/src/com/android/settings/wifi/WifiSlice.java b/src/com/android/settings/wifi/WifiSlice.java index 536be6f3862..b055bcfe04a 100644 --- a/src/com/android/settings/wifi/WifiSlice.java +++ b/src/com/android/settings/wifi/WifiSlice.java @@ -35,6 +35,7 @@ import android.os.Looper; import android.provider.SettingsSlicesContract; import android.text.TextUtils; +import androidx.annotation.VisibleForTesting; import androidx.core.graphics.drawable.IconCompat; import androidx.slice.Slice; import androidx.slice.builders.ListBuilder; @@ -61,6 +62,7 @@ import java.util.List; */ public class WifiSlice implements CustomSliceable { + /** * Backing Uri for the Wifi Slice. */ @@ -71,6 +73,9 @@ public class WifiSlice implements CustomSliceable { .appendPath(KEY_WIFI) .build(); + @VisibleForTesting + static final int DEFAULT_EXPANDED_ROW_COUNT = 3; + private final Context mContext; public WifiSlice(Context context) { @@ -115,26 +120,43 @@ public class WifiSlice implements CustomSliceable { .addEndItem(toggleSliceAction) .setPrimaryAction(primarySliceAction)); - if (isWifiEnabled) { - final List result = getBackgroundWorker().getResults(); - if (result != null && !result.isEmpty()) { - for (AccessPoint ap : result) { - listBuilder.addRow(getAccessPointRow(ap)); - } - listBuilder.setSeeMoreAction(primaryAction); + if (!isWifiEnabled) { + return listBuilder.build(); + } + + List result = getBackgroundWorker().getResults(); + if (result == null) { + result = new ArrayList<>(); + } + final int apCount = result.size(); + // Add AP rows + final CharSequence placeholder = mContext.getText(R.string.summary_placeholder); + for (int i = 0; i < DEFAULT_EXPANDED_ROW_COUNT; i++) { + if (i < apCount) { + listBuilder.addRow(getAccessPointRow(result.get(i))); + } else { + listBuilder.addRow(new RowBuilder() + .setTitle(placeholder) + .setSubtitle(placeholder)); } } - return listBuilder.build(); + // Add more button + return listBuilder + .setSeeMoreAction(primaryAction) + .build(); } private RowBuilder getAccessPointRow(AccessPoint accessPoint) { final String title = accessPoint.getConfigName(); final IconCompat levelIcon = IconCompat.createWithResource(mContext, com.android.settingslib.Utils.getWifiIconResource(accessPoint.getLevel())); + final CharSequence apSummary = accessPoint.getSettingsSummary(); final RowBuilder rowBuilder = new RowBuilder() .setTitleItem(levelIcon, ListBuilder.ICON_IMAGE) .setTitle(title) - .setSubtitle(accessPoint.getSettingsSummary()) + .setSubtitle(!TextUtils.isEmpty(apSummary) + ? apSummary + : mContext.getText(R.string.summary_placeholder)) .setPrimaryAction(new SliceAction( getAccessPointAction(accessPoint), levelIcon, title)); diff --git a/tests/robotests/src/com/android/settings/homepage/CardContentLoaderTest.java b/tests/robotests/src/com/android/settings/homepage/CardContentLoaderTest.java index a7527f37b1f..853cf20ec8a 100644 --- a/tests/robotests/src/com/android/settings/homepage/CardContentLoaderTest.java +++ b/tests/robotests/src/com/android/settings/homepage/CardContentLoaderTest.java @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import android.content.Context; import android.net.Uri; +import com.android.settings.homepage.deviceinfo.BatterySlice; import com.android.settings.homepage.deviceinfo.DataUsageSlice; import com.android.settings.homepage.deviceinfo.DeviceInfoSlice; import com.android.settings.homepage.deviceinfo.StorageSlice; @@ -54,17 +55,18 @@ public class CardContentLoaderTest { } @Test - public void createStaticCards_shouldReturnTwoCards() { + public void createStaticCards_shouldReturnFourCards() { final List defaultData = mCardContentLoader.createStaticCards(); - assertThat(defaultData).hasSize(2); + assertThat(defaultData).hasSize(3); } @Test - public void createStaticCards_shouldContainDataUsageAndDeviceInfo() { + public void createStaticCards_shouldContainCorrectCards() { final Uri dataUsage = DataUsageSlice.DATA_USAGE_CARD_URI; final Uri deviceInfo = DeviceInfoSlice.DEVICE_INFO_CARD_URI; - final List expectedUris = Arrays.asList(dataUsage, deviceInfo); + final Uri batteryInfo = BatterySlice.BATTERY_CARD_URI; + final List expectedUris = Arrays.asList(dataUsage, deviceInfo, batteryInfo); final List actualCardUris = mCardContentLoader.createStaticCards().stream().map( ContextualCard::getSliceUri).collect(Collectors.toList()); diff --git a/tests/robotests/src/com/android/settings/homepage/deviceinfo/BatterySliceTest.java b/tests/robotests/src/com/android/settings/homepage/deviceinfo/BatterySliceTest.java new file mode 100644 index 00000000000..8baaab4af49 --- /dev/null +++ b/tests/robotests/src/com/android/settings/homepage/deviceinfo/BatterySliceTest.java @@ -0,0 +1,77 @@ +/* + * 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.homepage.deviceinfo; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + +import android.content.Context; + +import androidx.core.graphics.drawable.IconCompat; +import androidx.slice.Slice; +import androidx.slice.SliceItem; +import androidx.slice.SliceMetadata; +import androidx.slice.SliceProvider; +import androidx.slice.core.SliceAction; +import androidx.slice.widget.SliceLiveData; + +import com.android.settings.R; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.SliceTester; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; + +import java.util.List; + +@RunWith(SettingsRobolectricTestRunner.class) +public class BatterySliceTest { + + private Context mContext; + private BatterySlice mBatterySlice; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + + // Set-up specs for SliceMetadata. + SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS); + + mBatterySlice = spy(new BatterySlice(mContext)); + } + + @Test + public void getSlice_shouldBeCorrectSliceContent() { + doNothing().when(mBatterySlice).loadBatteryInfo(); + doReturn("10%").when(mBatterySlice).getBatteryPercentString(); + doReturn("test").when(mBatterySlice).getSummary(); + final Slice slice = mBatterySlice.getSlice(); + final SliceMetadata metadata = SliceMetadata.from(mContext, slice); + final SliceAction primaryAction = metadata.getPrimaryAction(); + final IconCompat expectedIcon = IconCompat.createWithResource(mContext, + R.drawable.ic_settings_battery); + assertThat(primaryAction.getIcon().toString()).isEqualTo(expectedIcon.toString()); + + final List sliceItems = slice.getItems(); + SliceTester.assertTitle(sliceItems, mContext.getString(R.string.power_usage_summary_title)); + } +} diff --git a/tests/robotests/src/com/android/settings/testutils/SliceTester.java b/tests/robotests/src/com/android/settings/testutils/SliceTester.java index 49a84f28a6f..892f948fe19 100644 --- a/tests/robotests/src/com/android/settings/testutils/SliceTester.java +++ b/tests/robotests/src/com/android/settings/testutils/SliceTester.java @@ -213,7 +213,7 @@ public class SliceTester { for (SliceItem subTitleItem : titleItems) { if (TextUtils.equals(subTitleItem.getText(), title)) { hasTitle = true; - assertThat(subTitleItem.getText()).isEqualTo(title); + break; } } } diff --git a/tests/robotests/src/com/android/settings/wifi/WifiSliceTest.java b/tests/robotests/src/com/android/settings/wifi/WifiSliceTest.java index 742c8ad0a36..bd1ad4625ff 100644 --- a/tests/robotests/src/com/android/settings/wifi/WifiSliceTest.java +++ b/tests/robotests/src/com/android/settings/wifi/WifiSliceTest.java @@ -17,6 +17,11 @@ package com.android.settings.wifi; +import static android.app.slice.Slice.HINT_LIST_ITEM; +import static android.app.slice.SliceItem.FORMAT_SLICE; + +import static com.android.settings.wifi.WifiSlice.DEFAULT_EXPANDED_ROW_COUNT; + import static com.google.common.truth.Truth.assertThat; import android.content.Context; @@ -29,6 +34,7 @@ import androidx.slice.SliceItem; import androidx.slice.SliceMetadata; import androidx.slice.SliceProvider; import androidx.slice.core.SliceAction; +import androidx.slice.core.SliceQuery; import androidx.slice.widget.SliceLiveData; import com.android.settings.R; @@ -60,7 +66,7 @@ public class WifiSliceTest { } @Test - public void getWifiSlice_correctSliceContent() { + public void getWifiSlice_shouldHaveTitleAndToggle() { final Slice wifiSlice = mWifiSlice.getSlice(); final SliceMetadata metadata = SliceMetadata.from(mContext, wifiSlice); @@ -76,6 +82,17 @@ public class WifiSliceTest { SliceTester.assertTitle(sliceItems, mContext.getString(R.string.wifi_settings)); } + @Test + public void getWifiSlice_noAp_shouldReturnPlaceholder() { + final Slice wifiSlice = mWifiSlice.getSlice(); + + int rows = SliceQuery.findAll(wifiSlice, FORMAT_SLICE, HINT_LIST_ITEM, + null /* nonHints */).size(); + // All AP rows + title row + see more row + // (see more row will drop the last AP row, thus -1) + assertThat(rows).isEqualTo(DEFAULT_EXPANDED_ROW_COUNT - 1 + 2); + } + @Test public void handleUriChange_updatesWifi() { final Intent intent = mWifiSlice.getIntent();