1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.homepage.contextualcards.slices; 18 19 import static android.content.Context.MODE_PRIVATE; 20 21 import static com.android.settings.slices.CustomSliceRegistry.BATTERY_FIX_SLICE_URI; 22 23 import android.app.PendingIntent; 24 import android.app.settings.SettingsEnums; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.SharedPreferences; 28 import android.graphics.PorterDuff; 29 import android.graphics.PorterDuffColorFilter; 30 import android.graphics.drawable.Drawable; 31 import android.net.Uri; 32 import android.util.ArrayMap; 33 import android.view.View; 34 35 import androidx.annotation.VisibleForTesting; 36 import androidx.annotation.WorkerThread; 37 import androidx.core.graphics.drawable.IconCompat; 38 import androidx.slice.Slice; 39 import androidx.slice.builders.ListBuilder; 40 import androidx.slice.builders.ListBuilder.RowBuilder; 41 import androidx.slice.builders.SliceAction; 42 43 import com.android.internal.os.BatteryStatsHelper; 44 import com.android.settings.R; 45 import com.android.settings.SubSettings; 46 import com.android.settings.Utils; 47 import com.android.settings.fuelgauge.BatteryStatsHelperLoader; 48 import com.android.settings.fuelgauge.PowerUsageSummary; 49 import com.android.settings.fuelgauge.batterytip.BatteryTipLoader; 50 import com.android.settings.fuelgauge.batterytip.BatteryTipPreferenceController; 51 import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; 52 import com.android.settings.slices.CustomSliceable; 53 import com.android.settings.slices.SliceBackgroundWorker; 54 import com.android.settings.slices.SliceBuilderUtils; 55 import com.android.settingslib.utils.ThreadUtils; 56 57 import java.util.Arrays; 58 import java.util.List; 59 import java.util.Map; 60 61 public class BatteryFixSlice implements CustomSliceable { 62 63 @VisibleForTesting 64 static final String PREFS = "battery_fix_prefs"; 65 @VisibleForTesting 66 static final String KEY_CURRENT_TIPS_TYPE = "current_tip_type"; 67 static final String KEY_CURRENT_TIPS_STATE = "current_tip_state"; 68 69 // A map tracking which BatteryTip and which state of that tip is not important. 70 private static final Map<Integer, List<Integer>> UNIMPORTANT_BATTERY_TIPS; 71 72 static { 73 UNIMPORTANT_BATTERY_TIPS = new ArrayMap<>(); UNIMPORTANT_BATTERY_TIPS.put(BatteryTip.TipType.SUMMARY, Arrays.asList(BatteryTip.StateType.NEW, BatteryTip.StateType.HANDLED))74 UNIMPORTANT_BATTERY_TIPS.put(BatteryTip.TipType.SUMMARY, 75 Arrays.asList(BatteryTip.StateType.NEW, BatteryTip.StateType.HANDLED)); UNIMPORTANT_BATTERY_TIPS.put(BatteryTip.TipType.HIGH_DEVICE_USAGE, Arrays.asList(BatteryTip.StateType.NEW, BatteryTip.StateType.HANDLED))76 UNIMPORTANT_BATTERY_TIPS.put(BatteryTip.TipType.HIGH_DEVICE_USAGE, 77 Arrays.asList(BatteryTip.StateType.NEW, BatteryTip.StateType.HANDLED)); UNIMPORTANT_BATTERY_TIPS.put(BatteryTip.TipType.BATTERY_SAVER, Arrays.asList(BatteryTip.StateType.HANDLED))78 UNIMPORTANT_BATTERY_TIPS.put(BatteryTip.TipType.BATTERY_SAVER, 79 Arrays.asList(BatteryTip.StateType.HANDLED)); 80 } 81 82 private static final String TAG = "BatteryFixSlice"; 83 84 private final Context mContext; 85 BatteryFixSlice(Context context)86 public BatteryFixSlice(Context context) { 87 mContext = context; 88 } 89 90 @Override getUri()91 public Uri getUri() { 92 return BATTERY_FIX_SLICE_URI; 93 } 94 95 @Override getSlice()96 public Slice getSlice() { 97 final ListBuilder sliceBuilder = 98 new ListBuilder(mContext, BATTERY_FIX_SLICE_URI, ListBuilder.INFINITY) 99 .setAccentColor(COLOR_NOT_TINTED); 100 101 if (!isBatteryTipAvailableFromCache(mContext)) { 102 return buildBatteryGoodSlice(sliceBuilder, true /* isError */); 103 } 104 105 final SliceBackgroundWorker worker = SliceBackgroundWorker.getInstance(getUri()); 106 final List<BatteryTip> batteryTips = worker != null ? worker.getResults() : null; 107 108 if (batteryTips == null) { 109 // Because we need wait slice background worker return data 110 return buildBatteryGoodSlice(sliceBuilder, false /* isError */); 111 } 112 113 for (BatteryTip batteryTip : batteryTips) { 114 if (batteryTip.getState() == BatteryTip.StateType.INVISIBLE) { 115 continue; 116 } 117 final Drawable drawable = mContext.getDrawable(batteryTip.getIconId()); 118 final int iconTintColorId = batteryTip.getIconTintColorId(); 119 if (iconTintColorId != View.NO_ID) { 120 drawable.setColorFilter(new PorterDuffColorFilter( 121 mContext.getResources().getColor(iconTintColorId), 122 PorterDuff.Mode.SRC_IN)); 123 } 124 125 final IconCompat icon = Utils.createIconWithDrawable(drawable); 126 final SliceAction primaryAction = SliceAction.createDeeplink(getPrimaryAction(), 127 icon, 128 ListBuilder.ICON_IMAGE, 129 batteryTip.getTitle(mContext)); 130 sliceBuilder.addRow(new RowBuilder() 131 .setTitleItem(icon, ListBuilder.ICON_IMAGE) 132 .setTitle(batteryTip.getTitle(mContext)) 133 .setSubtitle(batteryTip.getSummary(mContext)) 134 .setPrimaryAction(primaryAction)); 135 break; 136 } 137 return sliceBuilder.build(); 138 } 139 140 @Override getIntent()141 public Intent getIntent() { 142 final String screenTitle = mContext.getText(R.string.power_usage_summary_title) 143 .toString(); 144 final Uri contentUri = new Uri.Builder() 145 .appendPath(BatteryTipPreferenceController.PREF_NAME).build(); 146 147 return SliceBuilderUtils.buildSearchResultPageIntent(mContext, 148 PowerUsageSummary.class.getName(), BatteryTipPreferenceController.PREF_NAME, 149 screenTitle, 150 SettingsEnums.SLICE) 151 .setClassName(mContext.getPackageName(), SubSettings.class.getName()) 152 .setData(contentUri); 153 } 154 155 @Override onNotifyChange(Intent intent)156 public void onNotifyChange(Intent intent) { 157 } 158 159 @Override getBackgroundWorkerClass()160 public Class getBackgroundWorkerClass() { 161 return BatteryTipWorker.class; 162 } 163 getPrimaryAction()164 private PendingIntent getPrimaryAction() { 165 final Intent intent = getIntent(); 166 return PendingIntent.getActivity(mContext, 0 /* requestCode */, intent, 0 /* flags */); 167 } 168 buildBatteryGoodSlice(ListBuilder sliceBuilder, boolean isError)169 private Slice buildBatteryGoodSlice(ListBuilder sliceBuilder, boolean isError) { 170 final IconCompat icon = IconCompat.createWithResource(mContext, 171 R.drawable.ic_battery_status_good_24dp); 172 final String title = mContext.getString(R.string.power_usage_summary_title); 173 final SliceAction primaryAction = SliceAction.createDeeplink(getPrimaryAction(), icon, 174 ListBuilder.ICON_IMAGE, title); 175 sliceBuilder.addRow(new RowBuilder() 176 .setTitleItem(icon, ListBuilder.ICON_IMAGE) 177 .setTitle(title) 178 .setPrimaryAction(primaryAction)) 179 .setIsError(isError); 180 return sliceBuilder.build(); 181 } 182 183 // TODO(b/114807643): we should find a better way to get current battery tip type quickly 184 // Now we save battery tip type to shared preference when battery level changes updateBatteryTipAvailabilityCache(Context context)185 public static void updateBatteryTipAvailabilityCache(Context context) { 186 ThreadUtils.postOnBackgroundThread(() -> refreshBatteryTips(context)); 187 } 188 189 190 @VisibleForTesting isBatteryTipAvailableFromCache(Context context)191 static boolean isBatteryTipAvailableFromCache(Context context) { 192 final SharedPreferences prefs = context.getSharedPreferences(PREFS, MODE_PRIVATE); 193 194 final int type = prefs.getInt(KEY_CURRENT_TIPS_TYPE, BatteryTip.TipType.SUMMARY); 195 final int state = prefs.getInt(KEY_CURRENT_TIPS_STATE, BatteryTip.StateType.INVISIBLE); 196 if (state == BatteryTip.StateType.INVISIBLE) { 197 // State is INVISIBLE, We should not show anything. 198 return false; 199 } 200 final boolean unimportant = UNIMPORTANT_BATTERY_TIPS.containsKey(type) 201 && UNIMPORTANT_BATTERY_TIPS.get(type).contains(state); 202 return !unimportant; 203 } 204 205 @WorkerThread 206 @VisibleForTesting refreshBatteryTips(Context context)207 static List<BatteryTip> refreshBatteryTips(Context context) { 208 final BatteryStatsHelperLoader statsLoader = new BatteryStatsHelperLoader(context); 209 final BatteryStatsHelper statsHelper = statsLoader.loadInBackground(); 210 final BatteryTipLoader loader = new BatteryTipLoader(context, statsHelper); 211 final List<BatteryTip> batteryTips = loader.loadInBackground(); 212 for (BatteryTip batteryTip : batteryTips) { 213 if (batteryTip.getState() != BatteryTip.StateType.INVISIBLE) { 214 context.getSharedPreferences(PREFS, MODE_PRIVATE) 215 .edit() 216 .putInt(KEY_CURRENT_TIPS_TYPE, batteryTip.getType()) 217 .putInt(KEY_CURRENT_TIPS_STATE, batteryTip.getState()) 218 .apply(); 219 break; 220 } 221 } 222 return batteryTips; 223 } 224 225 public static class BatteryTipWorker extends SliceBackgroundWorker<BatteryTip> { 226 227 private final Context mContext; 228 BatteryTipWorker(Context context, Uri uri)229 public BatteryTipWorker(Context context, Uri uri) { 230 super(context, uri); 231 mContext = context; 232 } 233 234 @Override onSlicePinned()235 protected void onSlicePinned() { 236 ThreadUtils.postOnBackgroundThread(() -> { 237 final List<BatteryTip> batteryTips = refreshBatteryTips(mContext); 238 updateResults(batteryTips); 239 }); 240 } 241 242 @Override onSliceUnpinned()243 protected void onSliceUnpinned() { 244 } 245 246 @Override close()247 public void close() { 248 } 249 } 250 } 251