platform-packages-apps-Settings / src / com / android / settings / fuelgauge / PowerUsageFeatureProviderImpl.java
PowerUsageFeatureProviderImpl.java
Raw
/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settings.fuelgauge;

import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Process;
import android.text.TextUtils;
import android.util.SparseIntArray;

import com.android.internal.os.BatterySipper;
import com.android.internal.util.ArrayUtils;

import com.android.settings.R;

import com.android.settingslib.utils.PowerUtil;

import java.time.Duration;

public class PowerUsageFeatureProviderImpl implements PowerUsageFeatureProvider {

    private static final String PACKAGE_CALENDAR_PROVIDER = "com.android.providers.calendar";
    private static final String PACKAGE_MEDIA_PROVIDER = "com.android.providers.media";
    private static final String PACKAGE_SYSTEMUI = "com.android.systemui";
    private static final String PACKAGE_GMS = "com.google.android.gms";
    private static final String PACKAGE_GCS = "com.google.android.apps.gcs";
    private static final String[] PACKAGES_SYSTEM = {PACKAGE_MEDIA_PROVIDER,
            PACKAGE_CALENDAR_PROVIDER, PACKAGE_SYSTEMUI};
    private static final String[] PACKAGES_SERVICE = {PACKAGE_GMS, PACKAGE_GCS};

    static final String AVERAGE_BATTERY_LIFE_COL = "average_battery_life";
    static final String BATTERY_ESTIMATE_BASED_ON_USAGE_COL = "is_based_on_usage";
    static final String BATTERY_ESTIMATE_COL = "battery_estimate";
    static final String BATTERY_LEVEL_COL = "battery_level";
    static final String IS_EARLY_WARNING_COL = "is_early_warning";
    static final String TIMESTAMP_COL = "timestamp_millis";
    static final int CUSTOMIZED_TO_USER = 1;
    static final int NEED_EARLY_WARNING = 1;

    protected PackageManager mPackageManager;
    protected Context mContext;

    public PowerUsageFeatureProviderImpl(Context context) {
        mPackageManager = context.getPackageManager();
        mContext = context.getApplicationContext();
    }

    @Override
    public boolean isTypeService(BatterySipper sipper) {
        final int uid = sipper.uidObj == null ? -1 : sipper.getUid();
        sipper.mPackages = mPackageManager.getPackagesForUid(uid);
        if (sipper.mPackages != null) {
            for (final String packageName : sipper.mPackages) {
                if (ArrayUtils.contains(PACKAGES_SERVICE, packageName)) {
                    return true;
                }
            }
        }

        return false;
    }

    @Override
    public boolean isTypeSystem(BatterySipper sipper) {
        final int uid = sipper.uidObj == null ? -1 : sipper.getUid();
        sipper.mPackages = mPackageManager.getPackagesForUid(uid);
        // Classify all the sippers to type system if the range of uid is 0...FIRST_APPLICATION_UID
        if (uid >= Process.ROOT_UID && uid < Process.FIRST_APPLICATION_UID) {
            return true;
        } else if (sipper.mPackages != null) {
            for (final String packageName : sipper.mPackages) {
                if (ArrayUtils.contains(PACKAGES_SYSTEM, packageName)) {
                    return true;
                }
            }
        }

        return false;
    }

    @Override
    public boolean isLocationSettingEnabled(String[] packages) {
        return false;
    }

    @Override
    public boolean isAdditionalBatteryInfoEnabled() {
        return false;
    }

    @Override
    public Intent getAdditionalBatteryInfoIntent() {
        return null;
    }

    @Override
    public boolean isAdvancedUiEnabled() {
        return true;
    }

    @Override
    public boolean isPowerAccountingToggleEnabled() {
        return true;
    }

    @Override
    public Estimate getEnhancedBatteryPrediction(Context context) {
        long dischargeTime = -1L;
        boolean basedOnUsage = false;
        Uri uri = this.getEnhancedBatteryPredictionUri();
        Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);

        // Return null if cursor is null or empty
        if (cursor == null || !cursor.moveToFirst()) {
            try {
                cursor.close();
            }
            catch (NullPointerException nullPointerException) {
                 // cursor might be null
            }
            return null;
        }

        // Check if estimate is usage based
        int colIndex = cursor.getColumnIndex(BATTERY_ESTIMATE_BASED_ON_USAGE_COL);
        if (colIndex != -1)
            basedOnUsage = cursor.getInt(colIndex) == 1;

        // Calculate average discharge time based on average battery life
        colIndex = cursor.getColumnIndex(AVERAGE_BATTERY_LIFE_COL);
        if (colIndex != -1) {
            long avgBattery = cursor.getLong(colIndex);
            if (avgBattery != -1L) {
                dischargeTime = Duration.ofMinutes(15L).toMillis();
                if (Duration.ofMillis(avgBattery).compareTo(Duration.ofDays(1L)) >= 0)
                    dischargeTime = Duration.ofHours(1L).toMillis();
                dischargeTime = PowerUtil.roundTimeToNearestThreshold(avgBattery, dischargeTime);
            }
        }

        colIndex = cursor.getColumnIndex(BATTERY_ESTIMATE_COL);
        Estimate enhancedEstimate = new Estimate(cursor.getLong(colIndex),
                                                 basedOnUsage, dischargeTime);
        cursor.close();
        return enhancedEstimate;
    }

    @Override
    public SparseIntArray getEnhancedBatteryPredictionCurve(Context context, long zeroTime) {
        SparseIntArray curve = new SparseIntArray();
        Uri uri = this.getEnhancedBatteryPredictionCurveUri();
        Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);

        // Return null if cursor is null or empty
        if (cursor == null || !cursor.moveToFirst()) {
            try {
                cursor.close();
            }
            catch (NullPointerException nullPointerException) {
                 // cursor might be null
            }
            return null;
        }

        // Get time/battery data indicies
        int timestamp = cursor.getColumnIndex(TIMESTAMP_COL);
        int batteryLevel = cursor.getColumnIndex(BATTERY_LEVEL_COL);

        // Add time/battery data to a SparseIntArray and shift time data relative to starting time
        while (cursor.moveToNext()) {
            curve.append((int)(cursor.getLong(timestamp) - zeroTime), cursor.getInt(batteryLevel));
        }

        // Cleanup
        try {
            cursor.close();
        }
        catch (NullPointerException nullPointerException) {
            // We already checked if cursor is null, so it shouldn't be dereferenced yet.
        }
        return curve;
    }

    private Uri getEnhancedBatteryPredictionCurveUri() {
        return new Uri.Builder().scheme("content")
                                .authority("com.google.android.apps.turbo.estimated_time_remaining")
                                .appendPath("discharge_curve").build();
    }

    private Uri getEnhancedBatteryPredictionUri() {
        return new Uri.Builder().scheme("content")
                                .authority("com.google.android.apps.turbo.estimated_time_remaining")
                                .appendPath("time_remaining").build();
    }

    @Override
    public boolean isEnhancedBatteryPredictionEnabled(Context context) {
        try {
            boolean turboEnabled = mPackageManager.getPackageInfo("com.google.android.apps.turbo",
                                    PackageManager.MATCH_DISABLED_COMPONENTS).applicationInfo.enabled;
            return turboEnabled;
        }
        catch (PackageManager.NameNotFoundException nameNotFoundException) {
            return false;
        }
    }

    @Override
    public String getEnhancedEstimateDebugString(String timeRemaining) {
        return null;
    }

    @Override
    public boolean isEstimateDebugEnabled() {
        return false;
    }

    @Override
    public String getOldEstimateDebugString(String timeRemaining) {
        return null;
    }

    @Override
    public String getAdvancedUsageScreenInfoString() {
        return mContext.getString(R.string.advanced_battery_graph_subtext);
    }

    @Override
    public boolean getEarlyWarningSignal(Context context, String id) {
        // Build early warning URI and create a cursor to read it
        Uri.Builder builder = new Uri.Builder().scheme("content")
                                            .authority("com.google.android.apps.turbo.estimated_time_remaining")
                                            .appendPath("early_warning")
                                            .appendPath("id");
        if (TextUtils.isEmpty(id)) {
            builder.appendPath(context.getPackageName());
        } else {
            builder.appendPath(id);
        }
        Cursor cursor = context.getContentResolver().query(builder.build(), null, null, null, null);

        // Return null if cursor is null or empty
        if (cursor == null || !cursor.moveToFirst()) {
            try {
                cursor.close();
            }
            catch (NullPointerException nullPointerException) {
                 // cursor might be null
            }
            return false;
        }

        // Check if early warning is available
        boolean earlyWarningAvailable  = cursor.getInt(cursor.getColumnIndex(IS_EARLY_WARNING_COL)) == 1;

        // Cleanup
        try {
            cursor.close();
        }
        catch (NullPointerException nullPointerException) {
            // We already checked if cursor is null, so it shouldn't be dereferenced yet.
        }

        return earlyWarningAvailable;
    }

    @Override
    public boolean isSmartBatterySupported() {
        return mContext.getResources().getBoolean(
                com.android.internal.R.bool.config_smart_battery_available);
    }

}