diff --git a/res/drawable/battery_tips_all_rounded_bg.xml b/res/drawable/battery_tips_all_rounded_bg.xml new file mode 100644 index 00000000000..4f61f544074 --- /dev/null +++ b/res/drawable/battery_tips_all_rounded_bg.xml @@ -0,0 +1,22 @@ + + + + + + + \ No newline at end of file diff --git a/res/drawable/battery_tips_half_rounded_bottom_bg.xml b/res/drawable/battery_tips_half_rounded_bottom_bg.xml new file mode 100644 index 00000000000..7766de63400 --- /dev/null +++ b/res/drawable/battery_tips_half_rounded_bottom_bg.xml @@ -0,0 +1,27 @@ + + + + + + + \ No newline at end of file diff --git a/res/drawable/battery_tips_half_rounded_top_bg.xml b/res/drawable/battery_tips_half_rounded_top_bg.xml new file mode 100644 index 00000000000..aba1a4fb329 --- /dev/null +++ b/res/drawable/battery_tips_half_rounded_top_bg.xml @@ -0,0 +1,27 @@ + + + + + + + \ No newline at end of file diff --git a/res/drawable/ic_battery_charger.xml b/res/drawable/ic_battery_charger.xml new file mode 100644 index 00000000000..4406a568a3d --- /dev/null +++ b/res/drawable/ic_battery_charger.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/res/drawable/ic_battery_tips_close.xml b/res/drawable/ic_battery_tips_close.xml new file mode 100644 index 00000000000..7ef571b52f9 --- /dev/null +++ b/res/drawable/ic_battery_tips_close.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/res/drawable/ic_battery_tips_close_icon.xml b/res/drawable/ic_battery_tips_close_icon.xml new file mode 100644 index 00000000000..b7664740450 --- /dev/null +++ b/res/drawable/ic_battery_tips_close_icon.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/ic_battery_tips_lightbulb.xml b/res/drawable/ic_battery_tips_lightbulb.xml new file mode 100644 index 00000000000..f1449f9bb5b --- /dev/null +++ b/res/drawable/ic_battery_tips_lightbulb.xml @@ -0,0 +1,25 @@ + + + + + \ No newline at end of file diff --git a/res/drawable/ic_battery_tips_thumb_down.xml b/res/drawable/ic_battery_tips_thumb_down.xml new file mode 100644 index 00000000000..cd7656b220f --- /dev/null +++ b/res/drawable/ic_battery_tips_thumb_down.xml @@ -0,0 +1,25 @@ + + + + + \ No newline at end of file diff --git a/res/drawable/ic_battery_tips_thumb_up.xml b/res/drawable/ic_battery_tips_thumb_up.xml new file mode 100644 index 00000000000..b1d4cb66c70 --- /dev/null +++ b/res/drawable/ic_battery_tips_thumb_up.xml @@ -0,0 +1,25 @@ + + + + + \ No newline at end of file diff --git a/res/layout/battery_tips_card.xml b/res/layout/battery_tips_card.xml new file mode 100644 index 00000000000..d2edb51c0ab --- /dev/null +++ b/res/layout/battery_tips_card.xml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/values/dimens.xml b/res/values/dimens.xml index a7a124a32c5..e272c15109b 100755 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -373,6 +373,10 @@ 1dp 2dp + + 4dp + 24dp + 174dp 28dp diff --git a/res/values/strings.xml b/res/values/strings.xml index ba5f72db554..8e45f9cd09d 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -9650,6 +9650,24 @@ Set battery usage for apps + + Optimize + + + Is this message helpful? + + + Turn on adaptive brightness to extend battery life + + + It will help reduce your daily battery drain by 10% + + + Reduce screen timeout to extend battery life + + + It will help reduce your daily battery drain by 10% + Unrestricted diff --git a/res/xml/power_usage_advanced.xml b/res/xml/power_usage_advanced.xml index 2a1a23c78fe..c1294535a74 100644 --- a/res/xml/power_usage_advanced.xml +++ b/res/xml/power_usage_advanced.xml @@ -21,6 +21,18 @@ android:title="@string/advanced_battery_title" settings:keywords="@string/keywords_battery_usage"> + + + + + + mHourlyViewModels; private OnBatteryUsageUpdatedListener mOnBatteryUsageUpdatedListener; private OnScreenOnTimeUpdatedListener mOnScreenOnTimeUpdatedListener; + private OnBatteryTipsUpdatedListener mOnBatteryTipsUpdatedListener; private final SettingsActivity mActivity; private final MetricsFeatureProvider mMetricsFeatureProvider; @@ -209,6 +224,10 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll mOnScreenOnTimeUpdatedListener = listener; } + void setOnBatteryTipsUpdatedListener(OnBatteryTipsUpdatedListener listener) { + mOnBatteryTipsUpdatedListener = listener; + } + void setBatteryHistoryMap( final Map> batteryHistoryMap) { Log.d(TAG, "setBatteryHistoryMap() " + (batteryHistoryMap == null ? "null" @@ -344,6 +363,10 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll } mOnBatteryUsageUpdatedListener.onBatteryUsageUpdated( slotUsageData, getSlotInformation(), isBatteryUsageMapNullOrEmpty()); + + if (mOnBatteryTipsUpdatedListener != null) { + mOnBatteryTipsUpdatedListener.onBatteryTipsUpdated(null, null); + } } return true; } diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreference.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreference.java new file mode 100644 index 00000000000..661d7c8aca3 --- /dev/null +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreference.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2023 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.fuelgauge.batteryusage; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageButton; +import android.widget.TextView; + +import androidx.preference.Preference; +import androidx.preference.PreferenceViewHolder; + +import com.android.settings.R; +import com.android.settings.fuelgauge.PowerUsageFeatureProvider; +import com.android.settings.overlay.FeatureFactory; + +import com.google.android.material.button.MaterialButton; + +/** + * A preference for displaying the battery tips card view. + */ +public class BatteryTipsCardPreference extends Preference implements View.OnClickListener { + + private static final String TAG = "BatteryTipsCardPreference"; + + private final PowerUsageFeatureProvider mPowerUsageFeatureProvider; + + private MaterialButton mActionButton; + private ImageButton mDismissButton; + private ImageButton mThumbUpButton; + private ImageButton mThumbDownButton; + private CharSequence mTitle; + private CharSequence mSummary; + + public BatteryTipsCardPreference(Context context, AttributeSet attrs) { + super(context, attrs); + setLayoutResource(R.layout.battery_tips_card); + setSelectable(false); + mPowerUsageFeatureProvider = FeatureFactory.getFactory(context) + .getPowerUsageFeatureProvider(context); + } + + @Override + public void setTitle(CharSequence title) { + mTitle = title; + notifyChanged(); + } + + @Override + public void setSummary(CharSequence summary) { + mSummary = summary; + notifyChanged(); + } + + @Override + public void onClick(View view) { + // TODO: replace with the settings anomaly obtained from detectSettingsAnomaly(); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder view) { + super.onBindViewHolder(view); + + ((TextView) view.findViewById(R.id.title)).setText(mTitle); + ((TextView) view.findViewById(R.id.summary)).setText(mSummary); + + mActionButton = (MaterialButton) view.findViewById(R.id.action_button); + mActionButton.setOnClickListener(this); + mDismissButton = (ImageButton) view.findViewById(R.id.dismiss_button); + mDismissButton.setOnClickListener(this); + + if (!mPowerUsageFeatureProvider.isBatteryTipsFeedbackEnabled()) { + return; + } + view.findViewById(R.id.tips_card) + .setBackgroundResource(R.drawable.battery_tips_half_rounded_top_bg); + view.findViewById(R.id.feedback_card).setVisibility(View.VISIBLE); + + mThumbUpButton = (ImageButton) view.findViewById(R.id.thumb_up); + mThumbUpButton.setOnClickListener(this); + mThumbDownButton = (ImageButton) view.findViewById(R.id.thumb_down); + mThumbDownButton.setOnClickListener(this); + } +} diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsController.java new file mode 100644 index 00000000000..bcedd4f5794 --- /dev/null +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsController.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2023 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.fuelgauge.batteryusage; + +import android.content.Context; + +import androidx.preference.PreferenceScreen; + +import com.android.settings.core.BasePreferenceController; +import com.android.settings.fuelgauge.PowerUsageFeatureProvider; +import com.android.settings.overlay.FeatureFactory; + +/** Controls the update for battery tips card */ +public class BatteryTipsController extends BasePreferenceController { + + private static final String TAG = "BatteryTipsController"; + private static final String ROOT_PREFERENCE_KEY = "battery_tips_category"; + private static final String CARD_PREFERENCE_KEY = "battery_tips_card"; + + private final PowerUsageFeatureProvider mPowerUsageFeatureProvider; + + private Context mPrefContext; + private BatteryTipsCardPreference mCardPreference; + + public BatteryTipsController(Context context) { + super(context, ROOT_PREFERENCE_KEY); + mPowerUsageFeatureProvider = FeatureFactory.getFactory(context) + .getPowerUsageFeatureProvider(context); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPrefContext = screen.getContext(); + mCardPreference = screen.findPreference(CARD_PREFERENCE_KEY); + } + + /** + * Update the card visibility and contents. + * @param title a string not extend 2 lines. + * @param summary a string not extend 10 lines. + */ + // TODO: replace parameters with SettingsAnomaly Data Proto + public void handleBatteryTipsCardUpdated(String title, String summary) { + if (!mPowerUsageFeatureProvider.isBatteryTipsEnabled()) { + mCardPreference.setVisible(false); + return; + } + if (title == null || summary == null) { + mCardPreference.setVisible(false); + return; + } + mCardPreference.setTitle(title); + mCardPreference.setSummary(summary); + mCardPreference.setVisible(true); + } + +} diff --git a/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java b/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java index 920670fa13f..6d14e1c72e4 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java @@ -83,6 +83,10 @@ public final class BootBroadcastReceiver extends BroadcastReceiver { recheckIntent.setClass(context, BootBroadcastReceiver.class); final long delayedTime = getRescheduleTimeForBootAction(context); mHandler.postDelayed(() -> context.sendBroadcast(recheckIntent), delayedTime); + + // Refreshes the usage source from UsageStatsManager when booting. + DatabaseUtils.removeUsageSource(context); + BatteryUsageLogUtils.writeLog(context, Action.RECHECK_JOB, "delay:" + delayedTime); } else if (ACTION_SETUP_WIZARD_FINISHED.equals(action)) { ElapsedTimeUtils.storeSuwFinishedTimestamp(context, System.currentTimeMillis()); diff --git a/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java b/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java index 2c98c4bd249..3fc44ccfd9b 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java +++ b/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java @@ -17,7 +17,6 @@ package com.android.settings.fuelgauge.batteryusage; import android.annotation.IntDef; import android.annotation.Nullable; -import android.app.usage.IUsageStatsManager; import android.app.usage.UsageEvents.Event; import android.app.usage.UsageStatsManager; import android.content.ContentValues; @@ -27,7 +26,6 @@ import android.database.Cursor; import android.os.BatteryUsageStats; import android.os.Build; import android.os.LocaleList; -import android.os.RemoteException; import android.os.UserHandle; import android.text.TextUtils; import android.text.format.DateFormat; @@ -67,6 +65,12 @@ public final class ConvertUtils { public static final int CONSUMER_TYPE_USER_BATTERY = 2; public static final int CONSUMER_TYPE_SYSTEM_BATTERY = 3; + public static final int DEFAULT_USAGE_SOURCE = UsageStatsManager.USAGE_SOURCE_CURRENT_ACTIVITY; + public static final int EMPTY_USAGE_SOURCE = -1; + + @VisibleForTesting + static int sUsageSource = EMPTY_USAGE_SOURCE; + private ConvertUtils() { } @@ -181,8 +185,7 @@ public final class ConvertUtils { /** Converts to {@link AppUsageEvent} from {@link Event} */ @Nullable public static AppUsageEvent convertToAppUsageEvent( - Context context, final IUsageStatsManager usageStatsManager, final Event event, - final long userId) { + Context context, final Event event, final long userId) { final String packageName = event.getPackageName(); if (packageName == null) { // See b/190609174: Event package names should never be null, but sometimes they are. @@ -207,7 +210,7 @@ public final class ConvertUtils { } final String effectivePackageName = - getEffectivePackageName(usageStatsManager, packageName, taskRootPackageName); + getEffectivePackageName(context, packageName, taskRootPackageName); try { final long uid = context .getPackageManager() @@ -323,9 +326,8 @@ public final class ConvertUtils { */ @VisibleForTesting static String getEffectivePackageName( - final IUsageStatsManager usageStatsManager, final String packageName, - final String taskRootPackageName) { - int usageSource = getUsageSource(usageStatsManager); + Context context, final String packageName, final String taskRootPackageName) { + final int usageSource = getUsageSource(context); switch (usageSource) { case UsageStatsManager.USAGE_SOURCE_TASK_ROOT_ACTIVITY: return !TextUtils.isEmpty(taskRootPackageName) @@ -370,18 +372,11 @@ public final class ConvertUtils { } } - /** - * Returns what App Usage Observers will consider the source of usage for an activity. - * - * @see UsageStatsManager#getUsageSource() - */ - private static int getUsageSource(final IUsageStatsManager usageStatsManager) { - try { - return usageStatsManager.getUsageSource(); - } catch (RemoteException e) { - Log.e(TAG, "Failed to getUsageSource", e); - return UsageStatsManager.USAGE_SOURCE_CURRENT_ACTIVITY; + private static int getUsageSource(Context context) { + if (sUsageSource == EMPTY_USAGE_SOURCE) { + sUsageSource = DatabaseUtils.getUsageSource(context); } + return sUsageSource; } private static AppUsageEventType getAppUsageEventType(final int eventType) { diff --git a/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java b/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java index 0f67e6a7a5a..1c851fd4ca9 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java +++ b/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java @@ -397,8 +397,8 @@ public class DataProcessManager { } // Generates the indexed AppUsagePeriod list data for each corresponding time slot for // further use. - mAppUsagePeriodMap = DataProcessor.generateAppUsagePeriodMap(mRawStartTimestamp, - mHourlyBatteryLevelsPerDay, mAppUsageEventList, mBatteryEventList); + mAppUsagePeriodMap = DataProcessor.generateAppUsagePeriodMap( + mContext, mHourlyBatteryLevelsPerDay, mAppUsageEventList, mBatteryEventList); } private void tryToGenerateFinalDataAndApplyCallback() { diff --git a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java index 6914c3043ea..badc35935fa 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java +++ b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java @@ -32,7 +32,6 @@ import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; import android.os.Process; import android.os.RemoteException; -import android.os.ServiceManager; import android.os.UidBatteryConsumer; import android.os.UserBatteryConsumer; import android.os.UserHandle; @@ -78,8 +77,6 @@ public final class DataProcessor { private static final int MIN_DAILY_DATA_SIZE = 2; private static final int MIN_TIMESTAMP_DATA_SIZE = 2; private static final int MAX_DIFF_SECONDS_OF_UPPER_TIMESTAMP = 5; - // Maximum total time value for each hourly slot cumulative data at most 2 hours. - private static final float TOTAL_HOURLY_TIME_THRESHOLD = DateUtils.HOUR_IN_MILLIS * 2; private static final long MIN_TIME_SLOT = DateUtils.HOUR_IN_MILLIS * 2; private static final String MEDIASERVER_PACKAGE_NAME = "mediaserver"; private static final String ANDROID_CORE_APPS_SHARED_USER_ID = "android.uid.shared"; @@ -111,11 +108,6 @@ public final class DataProcessor { @VisibleForTesting static Set sTestSystemAppsPackageNames; - @VisibleForTesting - static IUsageStatsManager sUsageStatsManager = - IUsageStatsManager.Stub.asInterface( - ServiceManager.getService(Context.USAGE_STATS_SERVICE)); - public static final String CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER = "CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER"; @@ -271,7 +263,7 @@ public final class DataProcessor { @Nullable public static Map>>>> generateAppUsagePeriodMap( - final long rawStartTimestamp, + Context context, final List hourlyBatteryLevelsPerDay, final List appUsageEventList, final List batteryEventList) { @@ -305,7 +297,7 @@ public final class DataProcessor { // The value could be null when there is no data in the hourly slot. dailyMap.put( hourlyIndex, - buildAppUsagePeriodList(hourlyAppUsageEventList, batteryEventList, + buildAppUsagePeriodList(context, hourlyAppUsageEventList, batteryEventList, startTimestamp, endTimestamp)); } } @@ -346,8 +338,7 @@ public final class DataProcessor { break; } final AppUsageEvent appUsageEvent = - ConvertUtils.convertToAppUsageEvent( - context, sUsageStatsManager, event, userId); + ConvertUtils.convertToAppUsageEvent(context, event, userId); if (appUsageEvent != null) { numEventsFetched++; appUsageEventList.add(appUsageEvent); @@ -661,8 +652,8 @@ public final class DataProcessor { @VisibleForTesting @Nullable static Map>> buildAppUsagePeriodList( - final List appUsageEvents, final List batteryEventList, - final long startTime, final long endTime) { + Context context, final List appUsageEvents, + final List batteryEventList, final long startTime, final long endTime) { if (appUsageEvents.isEmpty()) { return null; } @@ -702,7 +693,7 @@ public final class DataProcessor { final AppUsageEvent firstEvent = usageEvents.get(0); final long eventUserId = firstEvent.getUserId(); final String packageName = getEffectivePackageName( - sUsageStatsManager, + context, firstEvent.getPackageName(), firstEvent.getTaskRootPackageName()); usageEvents.addAll(deviceEvents); @@ -975,7 +966,7 @@ public final class DataProcessor { final long startTime = DatabaseUtils.getAppUsageStartTimestampOfUser( context, userID, earliestTimestamp); return loadAppUsageEventsForUserFromService( - sUsageStatsManager, startTime, now, userID, callingPackage); + DatabaseUtils.sUsageStatsManager, startTime, now, userID, callingPackage); } @Nullable diff --git a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java index 8d1a2f90991..ea1f3ed9e79 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java +++ b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java @@ -15,6 +15,8 @@ */ package com.android.settings.fuelgauge.batteryusage; +import android.app.usage.IUsageStatsManager; +import android.app.usage.UsageStatsManager; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; @@ -28,6 +30,8 @@ import android.os.BatteryManager; import android.os.BatteryUsageStats; import android.os.Handler; import android.os.Looper; +import android.os.RemoteException; +import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserManager; import android.util.Log; @@ -63,6 +67,7 @@ public final class DatabaseUtils { static final int DATA_RETENTION_INTERVAL_DAY = 9; static final String KEY_LAST_LOAD_FULL_CHARGE_TIME = "last_load_full_charge_time"; static final String KEY_LAST_UPLOAD_FULL_CHARGE_TIME = "last_upload_full_charge_time"; + static final String KEY_LAST_USAGE_SOURCE = "last_usage_source"; /** An authority name of the battery content provider. */ public static final String AUTHORITY = "com.android.settings.battery.usage.provider"; @@ -74,8 +79,6 @@ public final class DatabaseUtils { public static final String BATTERY_STATE_TABLE = "BatteryState"; /** A path name for app usage latest timestamp query. */ public static final String APP_USAGE_LATEST_TIMESTAMP_PATH = "appUsageLatestTimestamp"; - /** A class name for battery usage data provider. */ - public static final String SETTINGS_PACKAGE_PATH = "com.android.settings"; /** Key for query parameter timestamp used in BATTERY_CONTENT_URI **/ public static final String QUERY_KEY_TIMESTAMP = "timestamp"; /** Key for query parameter userid used in APP_USAGE_EVENT_URI **/ @@ -114,6 +117,11 @@ public final class DatabaseUtils { @VisibleForTesting static Supplier sFakeSupplier; + @VisibleForTesting + static IUsageStatsManager sUsageStatsManager = + IUsageStatsManager.Stub.asInterface( + ServiceManager.getService(Context.USAGE_STATS_SERVICE)); + private DatabaseUtils() { } @@ -468,6 +476,37 @@ public final class DatabaseUtils { SHARED_PREFS_FILE, Context.MODE_PRIVATE); } + static void removeUsageSource(Context context) { + final SharedPreferences sharedPreferences = getSharedPreferences(context); + if (sharedPreferences != null && sharedPreferences.contains(KEY_LAST_USAGE_SOURCE)) { + sharedPreferences.edit().remove(KEY_LAST_USAGE_SOURCE).apply(); + } + } + + /** + * Returns what App Usage Observers will consider the source of usage for an activity. + * + * @see UsageStatsManager#getUsageSource() + */ + static int getUsageSource(Context context) { + final SharedPreferences sharedPreferences = getSharedPreferences(context); + if (sharedPreferences != null && sharedPreferences.contains(KEY_LAST_USAGE_SOURCE)) { + return sharedPreferences + .getInt(KEY_LAST_USAGE_SOURCE, ConvertUtils.DEFAULT_USAGE_SOURCE); + } + int usageSource = ConvertUtils.DEFAULT_USAGE_SOURCE; + + try { + usageSource = sUsageStatsManager.getUsageSource(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to getUsageSource", e); + } + if (sharedPreferences != null) { + sharedPreferences.edit().putInt(KEY_LAST_USAGE_SOURCE, usageSource).apply(); + } + return usageSource; + } + static void recordDateTime(Context context, String preferenceKey) { final SharedPreferences sharedPreferences = getSharedPreferences(context); if (sharedPreferences != null) { @@ -564,7 +603,7 @@ public final class DatabaseUtils { private static Map> loadHistoryMapFromContentProvider( Context context, Uri batteryStateUri) { - context = DatabaseUtils.getParentContext(context); + context = getParentContext(context); if (context == null) { return null; } diff --git a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java index 7c4478e66c6..9a8680e2244 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java +++ b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java @@ -34,6 +34,8 @@ import androidx.loader.content.Loader; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.fuelgauge.BatteryBroadcastReceiver; +import com.android.settings.fuelgauge.PowerUsageFeatureProvider; +import com.android.settings.overlay.FeatureFactory; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.search.SearchIndexable; @@ -143,6 +145,16 @@ public class PowerUsageAdvanced extends PowerUsageBase { controllers.add(screenOnTimeController); controllers.add(batteryUsageBreakdownController); setBatteryChartPreferenceController(); + + final PowerUsageFeatureProvider powerUsageFeatureProvider = + FeatureFactory.getFactory(context).getPowerUsageFeatureProvider(context); + if (powerUsageFeatureProvider.isBatteryTipsEnabled()) { + BatteryTipsController batteryTipsController = new BatteryTipsController(context); + mBatteryChartPreferenceController.setOnBatteryTipsUpdatedListener( + batteryTipsController::handleBatteryTipsCardUpdated); + controllers.add(batteryTipsController); + } + return controllers; } diff --git a/src/com/android/settings/password/ChooseLockPassword.java b/src/com/android/settings/password/ChooseLockPassword.java index 09d192412ba..3a1532b56e5 100644 --- a/src/com/android/settings/password/ChooseLockPassword.java +++ b/src/com/android/settings/password/ChooseLockPassword.java @@ -523,6 +523,7 @@ public class ChooseLockPassword extends SettingsActivity { setupPasswordRequirementsView(headerLayout); mPasswordRestrictionView.setLayoutManager(new LinearLayoutManager(getActivity())); + mPasswordRestrictionView.setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE); mPasswordEntry = view.findViewById(R.id.password_entry); mPasswordEntry.setOnEditorActionListener(this); mPasswordEntry.addTextChangedListener(this); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImplTest.java index 1a43dbbe8d2..c9591a51b08 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImplTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImplTest.java @@ -67,6 +67,15 @@ public class PowerUsageFeatureProviderImplTest { assertThat(mPowerFeatureProvider.isBatteryUsageEnabled()).isTrue(); } + @Test + public void testIsBatteryTipsEnabled_returnFalse() { + assertThat(mPowerFeatureProvider.isBatteryTipsEnabled()).isFalse(); + } + + @Test + public void testIsBatteryTipsFeedbackEnabled_returnTrue() { + assertThat(mPowerFeatureProvider.isBatteryTipsFeedbackEnabled()).isTrue(); + } @Test public void testGetBatteryUsageListConsumePowerThreshold_return0() { assertThat(mPowerFeatureProvider.getBatteryUsageListConsumePowerThreshold()).isEqualTo(0.0); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTipTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTipTest.java index a5f1ab3f531..9f6e4e32a71 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTipTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTipTest.java @@ -85,7 +85,7 @@ public final class IncompatibleChargerTipTest { @Test public void getIcon_showIcon() { assertThat(mIncompatibleChargerTip.getIconId()) - .isEqualTo(R.drawable.ic_battery_alert_theme); + .isEqualTo(R.drawable.ic_battery_charger); } @Test diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreferenceTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreferenceTest.java new file mode 100644 index 00000000000..6f9a47416ae --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreferenceTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2023 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.fuelgauge.batteryusage; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; + +import android.content.Context; + +import com.android.settings.R; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public final class BatteryTipsCardPreferenceTest { + + private Context mContext; + private BatteryTipsCardPreference mBatteryTipsCardPreference; + + @Before + public void setUp() { + mContext = spy(RuntimeEnvironment.application); + mBatteryTipsCardPreference = new BatteryTipsCardPreference(mContext, /*attrs=*/ null); + } + + @Test + public void constructor_returnExpectedResult() { + assertThat(mBatteryTipsCardPreference.getLayoutResource()).isEqualTo( + R.layout.battery_tips_card); + } +} diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java index aa1ebd7b44c..566df5251d5 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java @@ -22,8 +22,10 @@ import static org.robolectric.Shadows.shadowOf; import android.app.AlarmManager; import android.app.Application; +import android.app.usage.UsageStatsManager; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import androidx.test.core.app.ApplicationProvider; @@ -40,7 +42,6 @@ import org.robolectric.Shadows; import org.robolectric.shadows.ShadowAlarmManager; import java.time.Clock; -import java.time.Duration; import java.util.List; import java.util.concurrent.TimeUnit; @@ -65,10 +66,12 @@ public final class BootBroadcastReceiverTest { BatteryTestUtils.insertDataToBatteryStateTable( mContext, Clock.systemUTC().millis(), "com.android.systemui"); mDao = database.batteryStateDao(); + clearSharedPreferences(); } @After public void tearDown() { + clearSharedPreferences(); mPeriodicJobManager.reset(); } @@ -82,8 +85,21 @@ public final class BootBroadcastReceiverTest { @Test public void onReceive_withBootCompletedIntent_refreshesJob() { + final SharedPreferences sharedPreferences = DatabaseUtils.getSharedPreferences(mContext); + sharedPreferences + .edit() + .putInt(DatabaseUtils.KEY_LAST_USAGE_SOURCE, + UsageStatsManager.USAGE_SOURCE_CURRENT_ACTIVITY) + .apply(); + mReceiver.onReceive(mContext, new Intent(Intent.ACTION_BOOT_COMPLETED)); + assertThat(mShadowAlarmManager.peekNextScheduledAlarm()).isNotNull(); + assertThat( + DatabaseUtils + .getSharedPreferences(mContext) + .contains(DatabaseUtils.KEY_LAST_USAGE_SOURCE)) + .isFalse(); } @Test @@ -133,15 +149,7 @@ public final class BootBroadcastReceiverTest { BootBroadcastReceiver.ACTION_PERIODIC_JOB_RECHECK); } - private void insertExpiredData(int shiftDay) { - final long expiredTimeInMs = - Clock.systemUTC().millis() - Duration.ofDays(shiftDay).toMillis(); - BatteryTestUtils.insertDataToBatteryStateTable( - mContext, expiredTimeInMs - 1, "com.android.systemui"); - BatteryTestUtils.insertDataToBatteryStateTable( - mContext, expiredTimeInMs, "com.android.systemui"); - // Ensures the testing environment is correct. - assertThat(mDao.getAllAfter(0)).hasSize(3); + private void clearSharedPreferences() { + DatabaseUtils.getSharedPreferences(mContext).edit().clear().apply(); } - } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java index 6b8073b68ee..3cbe8a44d1f 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java @@ -25,7 +25,6 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; -import android.app.usage.IUsageStatsManager; import android.app.usage.UsageEvents; import android.app.usage.UsageEvents.Event; import android.content.ContentValues; @@ -35,7 +34,6 @@ import android.database.MatrixCursor; import android.os.BatteryManager; import android.os.BatteryUsageStats; import android.os.LocaleList; -import android.os.RemoteException; import android.os.UserHandle; import com.android.settings.fuelgauge.batteryusage.db.AppUsageEventEntity; @@ -62,14 +60,13 @@ public final class ConvertUtilsTest { @Mock private BatteryUsageStats mBatteryUsageStats; @Mock - private IUsageStatsManager mUsageStatsManager; - @Mock private BatteryEntry mMockBatteryEntry; @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = spy(RuntimeEnvironment.application); + ConvertUtils.sUsageSource = ConvertUtils.EMPTY_USAGE_SOURCE; when(mContext.getPackageManager()).thenReturn(mMockPackageManager); } @@ -302,7 +299,7 @@ public final class ConvertUtilsTest { final long userId = 2; final AppUsageEvent appUsageEvent = ConvertUtils.convertToAppUsageEvent( - mContext, mUsageStatsManager, event, userId); + mContext, event, userId); assertThat(appUsageEvent.getTimestamp()).isEqualTo(101L); assertThat(appUsageEvent.getType()).isEqualTo(AppUsageEventType.ACTIVITY_RESUMED); assertThat(appUsageEvent.getPackageName()).isEqualTo("com.android.settings1"); @@ -322,8 +319,8 @@ public final class ConvertUtilsTest { when(mMockPackageManager.getPackageUidAsUser(any(), anyInt())).thenReturn(1001); final long userId = 1; - final AppUsageEvent appUsageEvent = ConvertUtils.convertToAppUsageEvent( - mContext, mUsageStatsManager, event, userId); + final AppUsageEvent appUsageEvent = + ConvertUtils.convertToAppUsageEvent(mContext, event, userId); assertThat(appUsageEvent.getTimestamp()).isEqualTo(101L); assertThat(appUsageEvent.getType()).isEqualTo(AppUsageEventType.DEVICE_SHUTDOWN); assertThat(appUsageEvent.getPackageName()).isEqualTo("com.android.settings1"); @@ -338,8 +335,8 @@ public final class ConvertUtilsTest { final Event event = new Event(); event.mPackage = null; - final AppUsageEvent appUsageEvent = ConvertUtils.convertToAppUsageEvent( - mContext, mUsageStatsManager, event, /*userId=*/ 0); + final AppUsageEvent appUsageEvent = + ConvertUtils.convertToAppUsageEvent(mContext, event, /*userId=*/ 0); assertThat(appUsageEvent).isNull(); } @@ -354,8 +351,8 @@ public final class ConvertUtilsTest { .thenThrow(new PackageManager.NameNotFoundException()); final long userId = 1; - final AppUsageEvent appUsageEvent = ConvertUtils.convertToAppUsageEvent( - mContext, mUsageStatsManager, event, userId); + final AppUsageEvent appUsageEvent = + ConvertUtils.convertToAppUsageEvent(mContext, event, userId); assertThat(appUsageEvent).isNull(); } @@ -450,51 +447,47 @@ public final class ConvertUtilsTest { } @Test - public void getEffectivePackageName_currentActivity_returnPackageName() throws RemoteException { - when(mUsageStatsManager.getUsageSource()).thenReturn(USAGE_SOURCE_CURRENT_ACTIVITY); + public void getEffectivePackageName_currentActivity_returnPackageName() { + ConvertUtils.sUsageSource = USAGE_SOURCE_CURRENT_ACTIVITY; final String packageName = "com.android.settings1"; final String taskRootPackageName = "com.android.settings2"; assertThat(ConvertUtils.getEffectivePackageName( - mUsageStatsManager, packageName, taskRootPackageName)) + mContext, packageName, taskRootPackageName)) .isEqualTo(packageName); } @Test - public void getEffectivePackageName_usageSourceThrowException_returnPackageName() - throws RemoteException { - when(mUsageStatsManager.getUsageSource()).thenThrow(new RemoteException()); + public void getEffectivePackageName_emptyUsageSource_returnPackageName() { final String packageName = "com.android.settings1"; final String taskRootPackageName = "com.android.settings2"; assertThat(ConvertUtils.getEffectivePackageName( - mUsageStatsManager, packageName, taskRootPackageName)) + mContext, packageName, taskRootPackageName)) .isEqualTo(packageName); } @Test - public void getEffectivePackageName_rootActivity_returnTaskRootPackageName() - throws RemoteException { - when(mUsageStatsManager.getUsageSource()).thenReturn(USAGE_SOURCE_TASK_ROOT_ACTIVITY); + public void getEffectivePackageName_rootActivity_returnTaskRootPackageName() { + ConvertUtils.sUsageSource = USAGE_SOURCE_TASK_ROOT_ACTIVITY; final String packageName = "com.android.settings1"; final String taskRootPackageName = "com.android.settings2"; assertThat(ConvertUtils.getEffectivePackageName( - mUsageStatsManager, packageName, taskRootPackageName)) + mContext, packageName, taskRootPackageName)) .isEqualTo(taskRootPackageName); } @Test - public void getEffectivePackageName_nullOrEmptyTaskRoot_returnPackageName() - throws RemoteException { - when(mUsageStatsManager.getUsageSource()).thenReturn(USAGE_SOURCE_TASK_ROOT_ACTIVITY); + public void getEffectivePackageName_nullOrEmptyTaskRoot_returnPackageName() { + ConvertUtils.sUsageSource = USAGE_SOURCE_TASK_ROOT_ACTIVITY; final String packageName = "com.android.settings1"; assertThat(ConvertUtils.getEffectivePackageName( - mUsageStatsManager, packageName, /*taskRootPackageName=*/ null)) + mContext, packageName, /*taskRootPackageName=*/ null)) .isEqualTo(packageName); assertThat(ConvertUtils.getEffectivePackageName( - mUsageStatsManager, packageName, /*taskRootPackageName=*/ "")) + mContext, packageName, /*taskRootPackageName=*/ "")) .isEqualTo(packageName); } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java index b610cfbf289..7f7fe43bb9e 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java @@ -72,7 +72,7 @@ public final class DataProcessManagerTest { MockitoAnnotations.initMocks(this); mContext = spy(RuntimeEnvironment.application); - DataProcessor.sUsageStatsManager = mUsageStatsManager; + DatabaseUtils.sUsageStatsManager = mUsageStatsManager; doReturn(mContext).when(mContext).getApplicationContext(); doReturn(mUserManager) .when(mContext) diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java index e2274e2d5e1..8bed054e91e 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java @@ -93,7 +93,7 @@ public final class DataProcessorTest { mPowerUsageFeatureProvider = mFeatureFactory.powerUsageFeatureProvider; DataProcessor.sTestSystemAppsPackageNames = Set.of(); - DataProcessor.sUsageStatsManager = mUsageStatsManager; + DatabaseUtils.sUsageStatsManager = mUsageStatsManager; doReturn(mIntent).when(mContext).registerReceiver( isA(BroadcastReceiver.class), isA(IntentFilter.class)); doReturn(100).when(mIntent).getIntExtra(eq(BatteryManager.EXTRA_SCALE), anyInt()); @@ -249,7 +249,7 @@ public final class DataProcessorTest { final Map>>>> periodMap = DataProcessor.generateAppUsagePeriodMap( - 14400000L, hourlyBatteryLevelsPerDay, appUsageEventList, new ArrayList<>()); + mContext, hourlyBatteryLevelsPerDay, appUsageEventList, new ArrayList<>()); assertThat(periodMap).hasSize(3); // Day 1 @@ -287,7 +287,8 @@ public final class DataProcessorTest { hourlyBatteryLevelsPerDay.add( new BatteryLevelData.PeriodBatteryLevelData(new ArrayList<>(), new ArrayList<>())); assertThat(DataProcessor.generateAppUsagePeriodMap( - 0L, hourlyBatteryLevelsPerDay, new ArrayList<>(), new ArrayList<>())).isNull(); + mContext, hourlyBatteryLevelsPerDay, new ArrayList<>(), new ArrayList<>())) + .isNull(); } @Test @@ -1644,7 +1645,7 @@ public final class DataProcessorTest { final Map>> appUsagePeriodMap = DataProcessor.buildAppUsagePeriodList( - appUsageEvents, new ArrayList<>(), 0, 5); + mContext, appUsageEvents, new ArrayList<>(), 0, 5); assertThat(appUsagePeriodMap).hasSize(2); final Map> userMap1 = appUsagePeriodMap.get(1L); @@ -1668,7 +1669,7 @@ public final class DataProcessorTest { @Test public void buildAppUsagePeriodList_emptyEventList_returnNull() { assertThat(DataProcessor.buildAppUsagePeriodList( - new ArrayList<>(), new ArrayList<>(), 0, 1)).isNull(); + mContext, new ArrayList<>(), new ArrayList<>(), 0, 1)).isNull(); } @Test @@ -1680,7 +1681,7 @@ public final class DataProcessorTest { AppUsageEventType.DEVICE_SHUTDOWN, /*timestamp=*/ 2)); assertThat(DataProcessor.buildAppUsagePeriodList( - appUsageEvents, new ArrayList<>(), 0, 3)).isNull(); + mContext, appUsageEvents, new ArrayList<>(), 0, 3)).isNull(); } @Test diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java index efce44e600f..24be7698375 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java @@ -16,6 +16,9 @@ package com.android.settings.fuelgauge.batteryusage; +import static android.app.usage.UsageStatsManager.USAGE_SOURCE_CURRENT_ACTIVITY; +import static android.app.usage.UsageStatsManager.USAGE_SOURCE_TASK_ROOT_ACTIVITY; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -23,15 +26,19 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; +import android.app.usage.IUsageStatsManager; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.database.MatrixCursor; import android.os.BatteryManager; import android.os.BatteryUsageStats; +import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; @@ -67,6 +74,7 @@ public final class DatabaseUtilsTest { @Mock private BatteryEntry mMockBatteryEntry2; @Mock private BatteryEntry mMockBatteryEntry3; @Mock private Context mMockContext; + @Mock private IUsageStatsManager mUsageStatsManager; @Before public void setUp() { @@ -77,6 +85,7 @@ public final class DatabaseUtilsTest { doReturn(mPackageManager).when(mMockContext).getPackageManager(); doReturn(mPackageManager).when(mContext).getPackageManager(); DatabaseUtils.getSharedPreferences(mContext).edit().clear().apply(); + DatabaseUtils.sUsageStatsManager = mUsageStatsManager; } @Test @@ -422,6 +431,71 @@ public final class DatabaseUtilsTest { assertThat(batteryHistMap).isEmpty(); } + @Test + public void removeUsageSource_hasNoData() { + DatabaseUtils.removeUsageSource(mContext); + assertThat( + DatabaseUtils + .getSharedPreferences(mContext) + .contains(DatabaseUtils.KEY_LAST_USAGE_SOURCE)) + .isFalse(); + } + + @Test + public void removeUsageSource_hasData_deleteUsageSource() { + final SharedPreferences sharedPreferences = DatabaseUtils.getSharedPreferences(mContext); + sharedPreferences + .edit() + .putInt(DatabaseUtils.KEY_LAST_USAGE_SOURCE, USAGE_SOURCE_TASK_ROOT_ACTIVITY) + .apply(); + + DatabaseUtils.removeUsageSource(mContext); + + assertThat( + DatabaseUtils + .getSharedPreferences(mContext) + .contains(DatabaseUtils.KEY_LAST_USAGE_SOURCE)) + .isFalse(); + } + + @Test + public void getUsageSource_hasData() { + final SharedPreferences sharedPreferences = DatabaseUtils.getSharedPreferences(mContext); + sharedPreferences + .edit() + .putInt(DatabaseUtils.KEY_LAST_USAGE_SOURCE, USAGE_SOURCE_TASK_ROOT_ACTIVITY) + .apply(); + + assertThat(DatabaseUtils.getUsageSource(mContext)) + .isEqualTo(USAGE_SOURCE_TASK_ROOT_ACTIVITY); + } + + @Test + public void getUsageSource_notHasData_writeLoadedData() throws RemoteException { + when(mUsageStatsManager.getUsageSource()).thenReturn(USAGE_SOURCE_TASK_ROOT_ACTIVITY); + + assertThat(DatabaseUtils.getUsageSource(mContext)) + .isEqualTo(USAGE_SOURCE_TASK_ROOT_ACTIVITY); + assertThat( + DatabaseUtils + .getSharedPreferences(mContext) + .getInt(DatabaseUtils.KEY_LAST_USAGE_SOURCE, USAGE_SOURCE_CURRENT_ACTIVITY)) + .isEqualTo(USAGE_SOURCE_TASK_ROOT_ACTIVITY); + } + + @Test + public void getUsageSource_throwException_writeDefaultData() throws RemoteException { + when(mUsageStatsManager.getUsageSource()).thenThrow(new RemoteException()); + + assertThat(DatabaseUtils.getUsageSource(mContext)) + .isEqualTo(USAGE_SOURCE_CURRENT_ACTIVITY); + assertThat( + DatabaseUtils + .getSharedPreferences(mContext) + .getInt(DatabaseUtils.KEY_LAST_USAGE_SOURCE, USAGE_SOURCE_CURRENT_ACTIVITY)) + .isEqualTo(USAGE_SOURCE_CURRENT_ACTIVITY); + } + @Test public void recordDateTime_writeDataIntoSharedPreferences() { final String preferenceKey = "test_preference_key";