Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 60f6a25a authored by Mill Chen's avatar Mill Chen
Browse files

Add Battery slice in Contextual Settings Homepage

- Add Battery card that implements CustomSliceable in Contextual
Settings Homepage.
- Add test case for Battery slice.
- Created a loadBatteryInfo method for BatterySlice.
- Add a map in CustomSliceManager to cache CustomSliceable instances,
let existing battery slice be able to update battery info.
- Use a flag to avoid triggering an infinite loop when calling
notifyChange in the callback function.

Bug: 114796623, 115971399
Test: manual, robotests
Change-Id: I4b785708bf8456c6c4de7cae4b44f8a060bccbae
parent d7a25690
Loading
Loading
Loading
Loading
+48 −43
Original line number Original line Diff line number Diff line
@@ -148,6 +148,20 @@ public class BatteryInfo {
        new AsyncTask<Void, Void, BatteryInfo>() {
        new AsyncTask<Void, Void, BatteryInfo>() {
            @Override
            @Override
            protected BatteryInfo doInBackground(Void... params) {
            protected BatteryInfo doInBackground(Void... params) {
                return getBatteryInfo(context, statsHelper, shortString);
            }

            @Override
            protected void onPostExecute(BatteryInfo batteryInfo) {
                final long startTime = System.currentTimeMillis();
                callback.onBatteryInfoLoaded(batteryInfo);
                BatteryUtils.logRuntime(LOG_TAG, "time for callback", startTime);
            }
        }.execute();
    }

    public static BatteryInfo getBatteryInfo(final Context context,
            final BatteryStatsHelper statsHelper, boolean shortString) {
        final BatteryStats stats;
        final BatteryStats stats;
        final long batteryStatsTime = System.currentTimeMillis();
        final long batteryStatsTime = System.currentTimeMillis();
        if (statsHelper == null) {
        if (statsHelper == null) {
@@ -166,10 +180,10 @@ public class BatteryInfo {
        final long elapsedRealtimeUs =
        final long elapsedRealtimeUs =
                PowerUtil.convertMsToUs(SystemClock.elapsedRealtime());
                PowerUtil.convertMsToUs(SystemClock.elapsedRealtime());


                Intent batteryBroadcast = context.registerReceiver(null,
        final Intent batteryBroadcast = context.registerReceiver(null,
                new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
                new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
        // 0 means we are discharging, anything else means charging
        // 0 means we are discharging, anything else means charging
                boolean discharging =
        final boolean discharging =
                batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) == 0;
                batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) == 0;


        if (discharging && provider != null
        if (discharging && provider != null
@@ -182,9 +196,9 @@ public class BatteryInfo {
                        estimate, elapsedRealtimeUs, shortString);
                        estimate, elapsedRealtimeUs, shortString);
            }
            }
        }
        }
                long prediction = discharging
        final long prediction = discharging
                ? stats.computeBatteryTimeRemaining(elapsedRealtimeUs) : 0;
                ? stats.computeBatteryTimeRemaining(elapsedRealtimeUs) : 0;
                Estimate estimate = new Estimate(
        final Estimate estimate = new Estimate(
                PowerUtil.convertUsToMs(prediction),
                PowerUtil.convertUsToMs(prediction),
                false, /* isBasedOnUsage */
                false, /* isBasedOnUsage */
                Estimate.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN);
                Estimate.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN);
@@ -193,15 +207,6 @@ public class BatteryInfo {
                estimate, elapsedRealtimeUs, shortString);
                estimate, elapsedRealtimeUs, shortString);
    }
    }


            @Override
            protected void onPostExecute(BatteryInfo batteryInfo) {
                final long startTime = System.currentTimeMillis();
                callback.onBatteryInfoLoaded(batteryInfo);
                BatteryUtils.logRuntime(LOG_TAG, "time for callback", startTime);
            }
        }.execute();
    }

    @WorkerThread
    @WorkerThread
    public static BatteryInfo getBatteryInfoOld(Context context, Intent batteryBroadcast,
    public static BatteryInfo getBatteryInfoOld(Context context, Intent batteryBroadcast,
            BatteryStats stats, long elapsedRealtimeUs, boolean shortString) {
            BatteryStats stats, long elapsedRealtimeUs, boolean shortString) {
+10 −11
Original line number Original line Diff line number Diff line
@@ -32,6 +32,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.VisibleForTesting;
import androidx.slice.Slice;
import androidx.slice.Slice;


import com.android.settings.homepage.deviceinfo.BatterySlice;
import com.android.settings.homepage.deviceinfo.DataUsageSlice;
import com.android.settings.homepage.deviceinfo.DataUsageSlice;
import com.android.settings.homepage.deviceinfo.DeviceInfoSlice;
import com.android.settings.homepage.deviceinfo.DeviceInfoSlice;
import com.android.settingslib.utils.AsyncLoaderCompat;
import com.android.settingslib.utils.AsyncLoaderCompat;
@@ -101,17 +102,15 @@ public class CardContentLoader extends AsyncLoaderCompat<List<ContextualCard>> {
                    .setCardType(ContextualCard.CardType.SLICE)
                    .setCardType(ContextualCard.CardType.SLICE)
                    .setIsHalfWidth(false)
                    .setIsHalfWidth(false)
                    .build());
                    .build());
            //TODO(b/115971399): Will change following values of SliceUri and Name
            add(new ContextualCard.Builder()
            // after landing these slice cards.
                    .setSliceUri(BatterySlice.BATTERY_CARD_URI)
//            add(new ContextualCard.Builder()
                    .setName(BatterySlice.PATH_BATTERY_INFO)
//                    .setSliceUri("content://com.android.settings.slices/battery_card")
                    .setPackageName(packageName)
//                    .setName(packageName + "/" + "battery_card")
                    .setRankingScore(rankingScore)
//                    .setPackageName(packageName)
                    .setAppVersion(appVersionCode)
//                    .setRankingScore(rankingScore)
                    .setCardType(ContextualCard.CardType.SLICE)
//                    .setAppVersion(appVersionCode)
                    .setIsHalfWidth(false)
//                    .setCardType(ContextualCard.CardType.SLICE)
                    .build());
//                    .setIsHalfWidth(true)
//                    .build());
            add(new ContextualCard.Builder()
            add(new ContextualCard.Builder()
                    .setSliceUri(DeviceInfoSlice.DEVICE_INFO_CARD_URI)
                    .setSliceUri(DeviceInfoSlice.DEVICE_INFO_CARD_URI)
                    .setName(DeviceInfoSlice.PATH_DEVICE_INFO)
                    .setName(DeviceInfoSlice.PATH_DEVICE_INFO)
+7 −0
Original line number Original line Diff line number Diff line
@@ -20,6 +20,7 @@ import static android.provider.SettingsSlicesContract.KEY_WIFI;


import android.annotation.Nullable;
import android.annotation.Nullable;


import com.android.settings.homepage.deviceinfo.BatterySlice;
import com.android.settings.homepage.deviceinfo.DataUsageSlice;
import com.android.settings.homepage.deviceinfo.DataUsageSlice;
import com.android.settings.homepage.deviceinfo.DeviceInfoSlice;
import com.android.settings.homepage.deviceinfo.DeviceInfoSlice;
import com.android.settings.homepage.deviceinfo.StorageSlice;
import com.android.settings.homepage.deviceinfo.StorageSlice;
@@ -63,12 +64,18 @@ public class SettingsContextualCardProvider extends ContextualCardProvider {
                        .setSliceUri(EmergencyInfoSlice.EMERGENCY_INFO_CARD_URI.toString())
                        .setSliceUri(EmergencyInfoSlice.EMERGENCY_INFO_CARD_URI.toString())
                        .setCardName(EmergencyInfoSlice.PATH_EMERGENCY_INFO_CARD)
                        .setCardName(EmergencyInfoSlice.PATH_EMERGENCY_INFO_CARD)
                        .build();
                        .build();
        final ContextualCard batteryInfoCard =
                ContextualCard.newBuilder()
                        .setSliceUri(BatterySlice.BATTERY_CARD_URI.toSafeString())
                        .setCardName(BatterySlice.PATH_BATTERY_INFO)
                        .build();
        final ContextualCardList cards = ContextualCardList.newBuilder()
        final ContextualCardList cards = ContextualCardList.newBuilder()
                .addCard(wifiCard)
                .addCard(wifiCard)
                .addCard(dataUsageCard)
                .addCard(dataUsageCard)
                .addCard(deviceInfoCard)
                .addCard(deviceInfoCard)
                .addCard(storageInfoCard)
                .addCard(storageInfoCard)
                .addCard(emergencyInfoCard)
                .addCard(emergencyInfoCard)
                .addCard(batteryInfoCard)
                .build();
                .build();


        return cards;
        return cards;
+156 −0
Original line number Original line Diff line number Diff line
/*
 * 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
+13 −3
Original line number Original line Diff line number Diff line
@@ -20,12 +20,14 @@ import android.content.Context;
import android.net.Uri;
import android.net.Uri;
import android.util.ArrayMap;
import android.util.ArrayMap;


import com.android.settings.homepage.deviceinfo.BatterySlice;
import com.android.settings.homepage.deviceinfo.DataUsageSlice;
import com.android.settings.homepage.deviceinfo.DataUsageSlice;
import com.android.settings.homepage.deviceinfo.DeviceInfoSlice;
import com.android.settings.homepage.deviceinfo.DeviceInfoSlice;
import com.android.settings.homepage.deviceinfo.StorageSlice;
import com.android.settings.homepage.deviceinfo.StorageSlice;
import com.android.settings.wifi.WifiSlice;
import com.android.settings.wifi.WifiSlice;


import java.util.Map;
import java.util.Map;
import java.util.WeakHashMap;


/**
/**
 * Manages custom {@link androidx.slice.Slice Slices}, which are all Slices not backed by
 * Manages custom {@link androidx.slice.Slice Slices}, which are all Slices not backed by
@@ -39,10 +41,12 @@ public class CustomSliceManager {
    protected final Map<Uri, Class<? extends CustomSliceable>> mUriMap;
    protected final Map<Uri, Class<? extends CustomSliceable>> mUriMap;


    private final Context mContext;
    private final Context mContext;
    private final Map<Uri, CustomSliceable> mSliceableCache;


    public CustomSliceManager(Context context) {
    public CustomSliceManager(Context context) {
        mContext = context.getApplicationContext();
        mContext = context.getApplicationContext();
        mUriMap = new ArrayMap<>();
        mUriMap = new ArrayMap<>();
        mSliceableCache = new WeakHashMap<>();
        addSlices();
        addSlices();
    }
    }


@@ -53,13 +57,18 @@ public class CustomSliceManager {
     * the only thing that should be needed to create the object.
     * the only thing that should be needed to create the object.
     */
     */
    public CustomSliceable getSliceableFromUri(Uri uri) {
    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) {
        if (clazz == null) {
            throw new IllegalArgumentException("No Slice found for uri: " + uri);
            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(DataUsageSlice.DATA_USAGE_CARD_URI, DataUsageSlice.class);
        mUriMap.put(DeviceInfoSlice.DEVICE_INFO_CARD_URI, DeviceInfoSlice.class);
        mUriMap.put(DeviceInfoSlice.DEVICE_INFO_CARD_URI, DeviceInfoSlice.class);
        mUriMap.put(StorageSlice.STORAGE_CARD_URI, StorageSlice.class);
        mUriMap.put(StorageSlice.STORAGE_CARD_URI, StorageSlice.class);
        mUriMap.put(BatterySlice.BATTERY_CARD_URI, BatterySlice.class);
    }
    }
}
}
Loading