Merge "DO NOT MERGE Implement USB High Temperature warning dialog(1/N)" into pi-dev

am: f147660c1e

Change-Id: Icdba0dc71c79a5d630be327d0ff3f9d0044ce60e
This commit is contained in:
Bill Lin
2018-12-09 18:14:56 -08:00
committed by android-build-merger
17 changed files with 1255 additions and 61 deletions

View File

@@ -0,0 +1,39 @@
<!--
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.
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/contentPanel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp">
<ScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/alarm_dialog_panel_padding"
android:paddingEnd="?android:attr/dialogPreferredPadding"
android:paddingStart="?android:attr/dialogPreferredPadding"
android:clipToPadding="false">
<TextView
android:id="@android:id/message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/high_temp_alarm_notify_message"
style="@android:style/TextAppearance.Material.Subhead"/>
</ScrollView>
</FrameLayout>

View File

@@ -0,0 +1,46 @@
<!--
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.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/title_template"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical|start"
android:paddingStart="?android:attr/dialogPreferredPadding"
android:paddingEnd="?android:attr/dialogPreferredPadding"
android:paddingTop="@dimen/alarm_dialog_panel_padding">
<ImageView
android:id="@android:id/icon"
android:layout_width="32dip"
android:layout_height="32dip"
android:layout_marginEnd="8dip"
android:scaleType="fitCenter"
android:tint="?android:attr/colorError"
android:src="?android:attr/alertDialogIcon"/>
<com.android.internal.widget.DialogTitle
android:id="@+id/alertTitle"
android:singleLine="true"
android:ellipsize="end"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="viewStart"
android:text="@string/high_temp_alarm_title"
android:importantForAccessibility="noHideDescendants"
style="?android:attr/windowTitleStyle"/>
</LinearLayout>

Binary file not shown.

View File

@@ -365,17 +365,31 @@
<bool name="quick_settings_show_full_alarm">false</bool>
<!-- Whether or not beep sound should be when overheat -->
<bool name="config_alarmTemperatureBeepSound">false</bool>
<!-- Whether to show a warning notification when the device reaches a certain temperature. -->
<integer name="config_showTemperatureWarning">0</integer>
<!-- Whether to show a alarm dialog when device of usb cable reaches a certain temperature. -->
<integer name="config_showTemperatureAlarm">0</integer>
<!-- Temp at which to show a warning notification if config_showTemperatureWarning is true.
If < 0, uses the skin temperature sensor shutdown value from
HardwarePropertiesManager#getDeviceTemperatures - config_warningTemperatureTolerance. -->
<integer name="config_warningTemperature">-1</integer>
<!-- Temp at which to show a alarm dialog if config_showTemperatureAlarm is true.
If < 0, uses the skin temperature sensor shutdown value of index[1] from
HardwarePropertiesManager#getDeviceTemperatures -->
<integer name="config_alarmTemperature">60</integer>
<!-- Fudge factor for how much below the shutdown temp to show the warning. -->
<integer name="config_warningTemperatureTolerance">2</integer>
<!-- Fudge factor for how much below the overheat temp to dismiss alarm. -->
<integer name="config_alarmTemperatureTolerance">5</integer>
<!-- Accessibility actions -->
<item type="id" name="action_split_task_to_left" />
<item type="id" name="action_split_task_to_right" />

View File

@@ -1023,4 +1023,11 @@
<!-- How much we expand the touchable region of the status bar below the notch to catch touches
that just start below the notch. -->
<dimen name="display_cutout_touchable_region_size">12dp</dimen>
<!-- Padding for overheat alarm dialog message of content -->
<dimen name="alarm_dialog_panel_padding">24dp</dimen>
<!-- Padding for overheat alarm dialog message of content -->
<dimen name="alarm_dialog_message_space">28dp</dimen>
</resources>

View File

@@ -2069,6 +2069,14 @@
<string name="high_temp_notif_message">Some features limited while phone cools down</string>
<!-- Text body for dialog alerting user that their phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=300] -->
<string name="high_temp_dialog_message">Your phone will automatically try to cool down. You can still use your phone, but it may run slower.\n\nOnce your phone has cooled down, it will run normally.</string>
<!-- Title for alarm dialog alerting user the usb adapter has reached a certain temperature that should disconnect charging cable immediately. [CHAR LIMIT=30] -->
<string name="high_temp_alarm_title">Unplug charger</string>
<!-- Text body for dialog alerting user the usb adapter has reached a certain temperature that should disconnect charging cable immediately. [CHAR LIMIT=300] -->
<string name="high_temp_alarm_notify_message">There\u2019s an issue charging this device. Unplug the power adapter and take care as the cable may be warm.</string>
<!-- Text link for user to see more detail about overheat alarm. [CHAR LIMIT=300] -->
<string name="high_temp_alarm_help_care_steps">See care steps</string>
<!-- Help link of context for usb overheat web page -->
<string name="high_temp_alarm_help_url" translatable="false">help_uri_usb_warm</string>
<!-- SysUI Tuner: Button to select lock screen shortcut [CHAR LIMIT=60] -->
<string name="lockscreen_shortcut_left">Left shortcut</string>

View File

@@ -0,0 +1,158 @@
/*
* 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.systemui.power;
import static android.content.Context.VIBRATOR_SERVICE;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import android.content.ContentResolver;
import android.content.Context;
import android.media.AudioAttributes;
import android.net.Uri;
import android.os.Handler;
import android.os.VibrationEffect;
import android.os.Vibrator;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.media.NotificationPlayer;
/**
* A Controller handle beep sound, vibration and TTS depend on state of OverheatAlarmDialog.
*/
public class OverheatAlarmController {
private static final String TAG = OverheatAlarmController.class.getSimpleName();
private static final int VIBRATION_INTERVAL = 2000;
private static final long[] VIBRATION_PATTERN = new long[]{0, 400, 200, 400, 200, 400, 200};
private static OverheatAlarmController sInstance;
private final Vibrator mVibrator;
private NotificationPlayer mPlayer;
private VibrationEffect mVibrationEffect;
private boolean mShouldVibrate;
/**
* The constructor only used to create singleton sInstance.
*/
private OverheatAlarmController(Context context) {
mVibrator = (Vibrator) context.getSystemService(VIBRATOR_SERVICE);
}
/**
* Get singleton OverheatAlarmController instance.
*/
public static OverheatAlarmController getInstance(Context context) {
if (context == null) {
throw new IllegalArgumentException();
}
if (sInstance == null) {
sInstance = new OverheatAlarmController(context);
}
return sInstance;
}
/**
* Starting alarm beep sound and vibration.
*/
@VisibleForTesting
public void startAlarm(Context context) {
if (mPlayer != null) {
return;
}
playSound(context);
startVibrate();
}
/**
* Stop alarming beep sound, vibrating, and TTS if initialized.
*/
@VisibleForTesting
public void stopAlarm() {
stopPlayer();
stopVibrate();
}
@VisibleForTesting
protected void playSound(Context context) {
Uri uri = new Uri.Builder()
.scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
.authority(context.getBasePackageName())
.appendPath(Integer.toString(R.raw.overheat_alarm)).build();
if (mPlayer == null) {
mPlayer = new NotificationPlayer(TAG);
}
mPlayer.setUsesWakeLock(context);
mPlayer.play(context, uri, true /* looping */, getAlertAudioAttributes());
}
@VisibleForTesting
protected void stopPlayer() {
if (mPlayer != null) {
mPlayer.stop();
mPlayer = null;
}
}
@VisibleForTesting
protected void startVibrate() {
mShouldVibrate = true;
if (mVibrationEffect == null) {
mVibrationEffect = VibrationEffect.createWaveform(VIBRATION_PATTERN, -1);
}
performVibrate();
}
@VisibleForTesting
protected void performVibrate() {
if (mShouldVibrate && mVibrator != null) {
mVibrator.vibrate(mVibrationEffect, getAlertAudioAttributes());
Handler.getMain().sendMessageDelayed(
obtainMessage(OverheatAlarmController::performVibrate, this),
VIBRATION_INTERVAL);
}
}
@VisibleForTesting
protected void stopVibrate() {
if (mVibrator != null) {
mVibrator.cancel();
}
mShouldVibrate = false;
}
/**
* Build AudioAttributes for mPlayer(NotificationPlayer) and vibrator
* Use the alarm channel so it can vibrate in DnD mode, unless alarms are
* specifically disabled in DnD.
*/
private static AudioAttributes getAlertAudioAttributes() {
AudioAttributes.Builder builder = new AudioAttributes.Builder();
builder.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION);
builder.setUsage(AudioAttributes.USAGE_ALARM);
// Set FLAG_BYPASS_INTERRUPTION_POLICY and FLAG_BYPASS_MUTE so that it enables
// audio in any DnD mode, even in total silence DnD mode (requires MODIFY_PHONE_STATE).
builder.setFlags(
AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY | AudioAttributes.FLAG_BYPASS_MUTE);
return builder.build();
}
}

View File

@@ -0,0 +1,181 @@
/*
* 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.systemui.power;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.UserHandle;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.TextView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
/**
* The alarm dialog shown when the device overheats.
* When the temperature exceeds a threshold, we're showing this dialog to notify the user.
* Once the dialog shows, a sound and vibration will be played until the user touches the dialog.
*/
public class OverheatAlarmDialog extends AlertDialog {
private final OverheatDialogDelegate mOverheatDialogDelegate;
private final View mContentView, mTitleView;
private static boolean sHasUserInteracted;
private OverheatAlarmDialog.PowerEventReceiver mPowerEventReceiver;
/**
* OverheatAlarmDialog should appear over system panels and keyguard.
*/
public OverheatAlarmDialog(Context context) {
super(context, R.style.Theme_SystemUI_Dialog_Alert);
mOverheatDialogDelegate = new OverheatDialogDelegate();
// Setup custom views, the purpose of set custom title and message is inject
// AccessibilityDelegate to solve beep sound and talk back mix problem
final LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
mContentView = inflater.inflate(R.layout.overheat_dialog_content, null);
mTitleView = inflater.inflate(R.layout.overheat_dialog_title, null);
setView(mContentView);
setCustomTitle(mTitleView);
setupDialog();
}
@Override
public void dismiss() {
sHasUserInteracted = false;
getContext().unregisterReceiver(mPowerEventReceiver);
super.dismiss();
}
private void setupDialog() {
getWindow().getAttributes().privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
// Register ACTION_SCREEN_OFF for power Key event.
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
mPowerEventReceiver = new OverheatAlarmDialog.PowerEventReceiver();
getContext().registerReceiverAsUser(mPowerEventReceiver, UserHandle.CURRENT, filter, null,
null);
}
@Override
public void setTitle(CharSequence title) {
if (mTitleView == null) {
super.setTitle(title);
return;
}
final TextView titleTextView = mTitleView.findViewById(R.id.alertTitle);
if (titleTextView != null) {
titleTextView.setText(title);
titleTextView.setAccessibilityDelegate(mOverheatDialogDelegate);
}
}
@Override
public void setMessage(CharSequence message) {
if (mContentView == null) {
super.setMessage(message);
return;
}
final TextView messageView = mContentView.findViewById(android.R.id.message);
if (messageView != null) {
messageView.setAccessibilityDelegate(mOverheatDialogDelegate);
messageView.requestAccessibilityFocus();
messageView.setText(message);
}
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (!sHasUserInteracted) {
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_BACK:
notifyAlarmBeepSoundChange();
sHasUserInteracted = true;
break;
}
}
return super.dispatchKeyEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// Stop beep sound when touch alarm dialog.
if (!sHasUserInteracted) {
if (ev.getAction() == MotionEvent.ACTION_DOWN
|| ev.getAction() == MotionEvent.ACTION_OUTSIDE) {
notifyAlarmBeepSoundChange();
sHasUserInteracted = true;
}
}
return super.dispatchTouchEvent(ev);
}
@VisibleForTesting
protected void notifyAlarmBeepSoundChange() {
this.getContext().sendBroadcast(new Intent(Intent.ACTION_ALARM_CHANGED).setPackage(
this.getContext().getPackageName())
.setFlags(Intent.FLAG_RECEIVER_FOREGROUND));
}
private final class PowerEventReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (!sHasUserInteracted) {
notifyAlarmBeepSoundChange();
sHasUserInteracted = true;
}
}
}
/**
* Implement AccessibilityDelegate to stop beep sound while title or message view get
* accessibility focus, in case the alarm beep sound mix up talk back description.
*/
private final class OverheatDialogDelegate extends View.AccessibilityDelegate {
@Override
public boolean performAccessibilityAction(View host, int action, Bundle args) {
if (action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS && !sHasUserInteracted) {
notifyAlarmBeepSoundChange();
sHasUserInteracted = true;
}
return super.performAccessibilityAction(host, action, args);
}
}
}

View File

@@ -16,6 +16,10 @@
package com.android.systemui.power;
import static android.content.DialogInterface.BUTTON_NEGATIVE;
import static android.content.DialogInterface.BUTTON_POSITIVE;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -47,10 +51,13 @@ import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.settingslib.Utils;
import com.android.settingslib.fuelgauge.BatterySaverUtils;
import com.android.settingslib.utils.PowerUtil;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.util.NotificationChannels;
import com.android.systemui.volume.Events;
import java.io.PrintWriter;
import java.text.NumberFormat;
@@ -111,6 +118,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
private final Context mContext;
private final NotificationManager mNoMan;
private final PowerManager mPowerMan;
private final KeyguardManager mKeyguard;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final Receiver mReceiver = new Receiver();
private final Intent mOpenBatterySettings = settings(Intent.ACTION_POWER_USAGE_SUMMARY);
@@ -134,25 +142,37 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
private boolean mHighTempWarning;
private SystemUIDialog mHighTempDialog;
private SystemUIDialog mThermalShutdownDialog;
@VisibleForTesting
protected OverheatAlarmDialog mOverheatAlarmDialog;
public PowerNotificationWarnings(Context context) {
mContext = context;
mNoMan = mContext.getSystemService(NotificationManager.class);
mPowerMan = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mKeyguard = mContext.getSystemService(KeyguardManager.class);
mReceiver.init();
}
@Override
public void dump(PrintWriter pw) {
pw.print("mWarning="); pw.println(mWarning);
pw.print("mPlaySound="); pw.println(mPlaySound);
pw.print("mInvalidCharger="); pw.println(mInvalidCharger);
pw.print("mShowing="); pw.println(SHOWING_STRINGS[mShowing]);
pw.print("mSaverConfirmation="); pw.println(mSaverConfirmation != null ? "not null" : null);
pw.print("mWarning=");
pw.println(mWarning);
pw.print("mPlaySound=");
pw.println(mPlaySound);
pw.print("mInvalidCharger=");
pw.println(mInvalidCharger);
pw.print("mShowing=");
pw.println(SHOWING_STRINGS[mShowing]);
pw.print("mSaverConfirmation=");
pw.println(mSaverConfirmation != null ? "not null" : null);
pw.print("mSaverEnabledConfirmation=");
pw.println(mSaverEnabledConfirmation != null ? "not null" : null);
pw.print("mHighTempWarning="); pw.println(mHighTempWarning);
pw.print("mHighTempDialog="); pw.println(mHighTempDialog != null ? "not null" : null);
pw.print("mHighTempWarning=");
pw.println(mHighTempWarning);
pw.print("mHighTempDialog=");
pw.println(mHighTempDialog != null ? "not null" : null);
pw.print("mOverheatAlarmDialog=");
pw.println(mOverheatAlarmDialog != null ? "not null" : null);
pw.print("mThermalShutdownDialog=");
pw.println(mThermalShutdownDialog != null ? "not null" : null);
}
@@ -370,6 +390,81 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
mNoMan.notifyAsUser(TAG_TEMPERATURE, SystemMessage.NOTE_HIGH_TEMP, n, UserHandle.ALL);
}
/**
* PowerUI detect thermal overheat, notify to popup alarm dialog.
* Alarm with beep sound, showing overheat alarm dialog until user click OK or link of help.
* Do not auto dismiss even temperature drop.
*
* @param overheat true if device overheat, temperature >= threshold.
* false if device temperature <= threshold tolerance after overheat
* alarmed.
* @param shouldBeepSound true alarm beep sound until user interactive with device
* false showing alarm dialog only
*/
@Override
public void notifyHighTemperatureAlarm(boolean overheat, boolean shouldBeepSound) {
// Overheat and non-null dialog are XOR(exclusive or) relationship
if (overheat ^ (mOverheatAlarmDialog != null)) {
// b/120188825 Since notifyHighTemperatureAlarm() could be triggered by
// non-ui thread such as ThermalEventListener.notifyThrottling()
mHandler.post(() -> setOverheatAlarmDialogShowing(overheat));
setAlarmShouldSound(shouldBeepSound);
}
}
/**
* Showing overheat alarm dialog until user click OK button or link of help to dismiss
*
* @param shouldShow whether to show overheat alarm dialog.
*/
protected void setOverheatAlarmDialogShowing(boolean shouldShow) {
if (shouldShow && mOverheatAlarmDialog == null) {
OverheatAlarmDialog d = new OverheatAlarmDialog(mContext);
d.setCancelable(false);
d.setButton(BUTTON_NEGATIVE,
mContext.getString(R.string.high_temp_alarm_help_care_steps),
(dialogInterface, i) -> {
final String contextString = mContext.getString(
R.string.high_temp_alarm_help_url);
final Intent helpIntent = new Intent();
helpIntent.setClassName("com.android.settings",
"com.android.settings.HelpTrampoline");
helpIntent.putExtra(Intent.EXTRA_TEXT, contextString);
Dependency.get(ActivityStarter.class).startActivity(helpIntent,
true /* dismissShade */, resultCode -> {
mOverheatAlarmDialog = null;
});
});
d.setButton(BUTTON_POSITIVE, mContext.getString(com.android.internal.R.string.ok),
(dialogInterface, i) -> mOverheatAlarmDialog = null);
d.setOnDismissListener(dialogInterface -> {
mOverheatAlarmDialog = null;
Events.writeEvent(mContext, Events.EVENT_DISMISS_OVERHEAT_ALARM,
Events.DISMISS_REASON_DONE_CLICKED,
mKeyguard.isKeyguardLocked());
});
d.show();
Events.writeEvent(mContext, Events.EVENT_SHOW_OVERHEAT_ALARM,
Events.SHOW_REASON_OVERHEAD_ALARM_CHANGED,
mKeyguard.isKeyguardLocked());
mOverheatAlarmDialog = d;
}
}
/**
* Whether to alarm beep sound when overheat dialog showing.
*
* @param shouldSound whether to alarm beep sound.
*/
protected void setAlarmShouldSound(boolean shouldSound) {
Log.d(TAG, "setAlarmShouldSound, " + shouldSound);
if (shouldSound) {
OverheatAlarmController.getInstance(mContext).startAlarm(mContext);
} else {
OverheatAlarmController.getInstance(mContext).stopAlarm();
}
}
private void showHighTemperatureDialog() {
if (mHighTempDialog != null) return;
final SystemUIDialog d = new SystemUIDialog(mContext);
@@ -643,6 +738,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
filter.addAction(ACTION_ENABLE_AUTO_SAVER);
filter.addAction(ACTION_AUTO_SAVER_NO_THANKS);
filter.addAction(ACTION_DISMISS_AUTO_SAVER_SUGGESTION);
filter.addAction(Intent.ACTION_ALARM_CHANGED);
mContext.registerReceiverAsUser(this, UserHandle.ALL, filter,
android.Manifest.permission.DEVICE_POWER, mHandler);
}
@@ -682,6 +778,8 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
} else if (ACTION_AUTO_SAVER_NO_THANKS.equals(action)) {
dismissAutoSaverSuggestion();
BatterySaverUtils.suppressAutoBatterySaver(context);
} else if (Intent.ACTION_ALARM_CHANGED.equals(action)) {
setAlarmShouldSound(false /* mHasUserInteracted */);
}
}
}

View File

@@ -54,12 +54,15 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.time.Duration;
import java.util.Arrays;
import java.util.Locale;
public class PowerUI extends SystemUI {
static final String TAG = "PowerUI";
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final long TEMPERATURE_INTERVAL = 30 * DateUtils.SECOND_IN_MILLIS;
private static final long TEMPERATURE_LOGGING_INTERVAL = DateUtils.HOUR_IN_MILLIS;
private static final int TEMPERATURE_OVERHEAT_WARNING = 0;
private static final int TEMPERATURE_OVERHEAT_ALARM = 1;
private static final int MAX_RECENT_TEMPS = 125; // TEMPERATURE_LOGGING_INTERVAL plus a buffer
static final long THREE_HOURS_IN_MILLIS = DateUtils.HOUR_IN_MILLIS * 3;
private static final int CHARGE_CYCLE_PERCENT_RESET = 45;
@@ -80,15 +83,22 @@ public class PowerUI extends SystemUI {
private Estimate mLastEstimate;
private boolean mLowWarningShownThisChargeCycle;
private boolean mSevereWarningShownThisChargeCycle;
private boolean mEnableTemperatureWarning;
private boolean mEnableTemperatureAlarm;
private boolean mIsOverheatAlarming;
private int mLowBatteryAlertCloseLevel;
private final int[] mLowBatteryReminderLevels = new int[2];
private long mScreenOffTime = -1;
private float mThresholdTemp;
private float[] mRecentTemps = new float[MAX_RECENT_TEMPS];
private int mNumTemps;
private float mThresholdWarningTemp;
private float mThresholdAlarmTemp;
private float mThresholdAlarmTempTolerance;
private float[] mRecentSkinTemps = new float[MAX_RECENT_TEMPS];
private float[] mRecentAlarmTemps = new float[MAX_RECENT_TEMPS];
private int mWarningNumTemps;
private int mAlarmNumTemps;
private long mNextLogTime;
private IThermalService mThermalService;
@@ -98,7 +108,7 @@ public class PowerUI extends SystemUI {
// by using the same instance (method references are not guaranteed to be the same object
// We create a method reference here so that we are guaranteed that we can remove a callback
// each time they are created).
private final Runnable mUpdateTempCallback = this::updateTemperatureWarning;
private final Runnable mUpdateTempCallback = this::updateTemperature;
public void start() {
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
@@ -126,7 +136,7 @@ public class PowerUI extends SystemUI {
// to the temperature being too high.
showThermalShutdownDialog();
initTemperatureWarning();
initTemperature();
}
@Override
@@ -135,7 +145,7 @@ public class PowerUI extends SystemUI {
// Safe to modify mLastConfiguration here as it's only updated by the main thread (here).
if ((mLastConfiguration.updateFrom(newConfig) & mask) != 0) {
mHandler.post(this::initTemperatureWarning);
mHandler.post(this::initTemperature);
}
}
@@ -193,6 +203,7 @@ public class PowerUI extends SystemUI {
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_USER_SWITCHED);
filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
mContext.registerReceiver(this, filter, null, mHandler);
}
@@ -258,6 +269,8 @@ public class PowerUI extends SystemUI {
mScreenOffTime = -1;
} else if (Intent.ACTION_USER_SWITCHED.equals(action)) {
mWarnings.userSwitched();
} else if (Intent.ACTION_POWER_DISCONNECTED.equals(action)) {
updateTemperature();
} else {
Slog.w(TAG, "unknown intent: " + intent);
}
@@ -364,18 +377,51 @@ public class PowerUI extends SystemUI {
return canShowWarning || canShowSevereWarning;
}
private void initTemperatureWarning() {
ContentResolver resolver = mContext.getContentResolver();
Resources resources = mContext.getResources();
if (Settings.Global.getInt(resolver, Settings.Global.SHOW_TEMPERATURE_WARNING,
resources.getInteger(R.integer.config_showTemperatureWarning)) == 0) {
private void initTemperature() {
initTemperatureWarning();
initTemperatureAlarm();
if (mEnableTemperatureWarning || mEnableTemperatureAlarm) {
bindThermalService();
}
}
private void initTemperatureAlarm() {
mEnableTemperatureAlarm = mContext.getResources().getInteger(
R.integer.config_showTemperatureAlarm) != 0;
if (!mEnableTemperatureAlarm) {
return;
}
mThresholdTemp = Settings.Global.getFloat(resolver, Settings.Global.WARNING_TEMPERATURE,
float[] throttlingTemps = mHardwarePropertiesManager.getDeviceTemperatures(
HardwarePropertiesManager.DEVICE_TEMPERATURE_SKIN,
HardwarePropertiesManager.TEMPERATURE_THROTTLING);
if (throttlingTemps == null || throttlingTemps.length < TEMPERATURE_OVERHEAT_ALARM + 1) {
mThresholdAlarmTemp = mContext.getResources().getInteger(
R.integer.config_alarmTemperature);
} else {
mThresholdAlarmTemp = throttlingTemps[TEMPERATURE_OVERHEAT_ALARM];
}
mThresholdAlarmTempTolerance = mThresholdAlarmTemp - mContext.getResources().getInteger(
R.integer.config_alarmTemperatureTolerance);
Log.d(TAG, "mThresholdAlarmTemp=" + mThresholdAlarmTemp + ", mThresholdAlarmTempTolerance="
+ mThresholdAlarmTempTolerance);
}
private void initTemperatureWarning() {
ContentResolver resolver = mContext.getContentResolver();
Resources resources = mContext.getResources();
mEnableTemperatureWarning = Settings.Global.getInt(resolver,
Settings.Global.SHOW_TEMPERATURE_WARNING,
resources.getInteger(R.integer.config_showTemperatureWarning)) != 0;
if (!mEnableTemperatureWarning) {
return;
}
mThresholdWarningTemp = Settings.Global.getFloat(resolver,
Settings.Global.WARNING_TEMPERATURE,
resources.getInteger(R.integer.config_warningTemperature));
if (mThresholdTemp < 0f) {
if (mThresholdWarningTemp < 0f) {
// Get the shutdown temperature, adjust for warning tolerance.
float[] throttlingTemps = mHardwarePropertiesManager.getDeviceTemperatures(
HardwarePropertiesManager.DEVICE_TEMPERATURE_SKIN,
@@ -385,10 +431,12 @@ public class PowerUI extends SystemUI {
|| throttlingTemps[0] == HardwarePropertiesManager.UNDEFINED_TEMPERATURE) {
return;
}
mThresholdTemp = throttlingTemps[0] -
resources.getInteger(R.integer.config_warningTemperatureTolerance);
mThresholdWarningTemp = throttlingTemps[0] - resources.getInteger(
R.integer.config_warningTemperatureTolerance);
}
}
private void bindThermalService() {
if (mThermalService == null) {
// Enable push notifications of throttling from vendor thermal
// management subsystem via thermalservice, in addition to our
@@ -416,7 +464,7 @@ public class PowerUI extends SystemUI {
mHandler.removeCallbacks(mUpdateTempCallback);
// We have passed all of the checks, start checking the temp
updateTemperatureWarning();
updateTemperature();
}
private void showThermalShutdownDialog() {
@@ -426,38 +474,110 @@ public class PowerUI extends SystemUI {
}
}
/**
* Update temperature depend on config, there are type types of messages by design
* TEMPERATURE_OVERHEAT_WARNING Send generic notification to notify user
* TEMPERATURE_OVERHEAT_ALARM popup emergency Dialog for user
*/
@VisibleForTesting
protected void updateTemperatureWarning() {
protected void updateTemperature() {
if (!mEnableTemperatureWarning && !mEnableTemperatureAlarm) {
return;
}
float[] temps = mHardwarePropertiesManager.getDeviceTemperatures(
HardwarePropertiesManager.DEVICE_TEMPERATURE_SKIN,
HardwarePropertiesManager.TEMPERATURE_CURRENT);
if (temps == null) {
Log.e(TAG, "Can't query current temperature value from HardProps SKIN type");
return;
}
final float[] temps_compat;
if (temps.length < TEMPERATURE_OVERHEAT_ALARM + 1) {
temps_compat = new float[] { temps[0], temps[0] };
} else {
temps_compat = temps;
}
if (mEnableTemperatureWarning) {
updateTemperatureWarning(temps_compat);
logTemperatureStats(TEMPERATURE_OVERHEAT_WARNING);
}
if (mEnableTemperatureAlarm) {
updateTemperatureAlarm(temps_compat);
logTemperatureStats(TEMPERATURE_OVERHEAT_ALARM);
}
mHandler.postDelayed(mUpdateTempCallback, TEMPERATURE_INTERVAL);
}
/**
* Update legacy overheat warning notification from skin temperatures
*
* @param temps the array include two types of value from DEVICE_TEMPERATURE_SKIN
* this function only obtain the value of temps[TEMPERATURE_OVERHEAT_WARNING]
*/
@VisibleForTesting
protected void updateTemperatureWarning(float[] temps) {
if (temps.length != 0) {
float temp = temps[0];
mRecentTemps[mNumTemps++] = temp;
float temp = temps[TEMPERATURE_OVERHEAT_WARNING];
mRecentSkinTemps[mWarningNumTemps++] = temp;
StatusBar statusBar = getComponent(StatusBar.class);
if (statusBar != null && !statusBar.isDeviceInVrMode()
&& temp >= mThresholdTemp) {
logAtTemperatureThreshold(temp);
&& temp >= mThresholdWarningTemp) {
logAtTemperatureThreshold(TEMPERATURE_OVERHEAT_WARNING, temp,
mThresholdWarningTemp);
mWarnings.showHighTemperatureWarning();
} else {
mWarnings.dismissHighTemperatureWarning();
}
}
logTemperatureStats();
mHandler.postDelayed(mUpdateTempCallback, TEMPERATURE_INTERVAL);
}
private void logAtTemperatureThreshold(float temp) {
/**
* Update overheat alarm from skin temperatures
* OEM can config alarm with beep sound by config_alarmTemperatureBeepSound
*
* @param temps the array include two types of value from DEVICE_TEMPERATURE_SKIN
* this function only obtain the value of temps[TEMPERATURE_OVERHEAT_ALARM]
*/
@VisibleForTesting
protected void updateTemperatureAlarm(float[] temps) {
if (temps.length != 0) {
final float temp = temps[TEMPERATURE_OVERHEAT_ALARM];
final boolean shouldBeepSound = mContext.getResources().getBoolean(
R.bool.config_alarmTemperatureBeepSound);
mRecentAlarmTemps[mAlarmNumTemps++] = temp;
if (temp >= mThresholdAlarmTemp && !mIsOverheatAlarming) {
mWarnings.notifyHighTemperatureAlarm(true /* overheat */, shouldBeepSound);
mIsOverheatAlarming = true;
} else if (temp <= mThresholdAlarmTempTolerance && mIsOverheatAlarming) {
mWarnings.notifyHighTemperatureAlarm(false /* overheat */, false /* beepSound */);
mIsOverheatAlarming = false;
}
logAtTemperatureThreshold(TEMPERATURE_OVERHEAT_ALARM, temp, mThresholdAlarmTemp);
}
}
private void logAtTemperatureThreshold(int type, float temp, float thresholdTemp) {
StringBuilder sb = new StringBuilder();
sb.append("currentTemp=").append(temp)
.append(",thresholdTemp=").append(mThresholdTemp)
.append(",overheatType=").append(type)
.append(",isOverheatAlarm=").append(mIsOverheatAlarming)
.append(",thresholdTemp=").append(thresholdTemp)
.append(",batteryStatus=").append(mBatteryStatus)
.append(",recentTemps=");
for (int i = 0; i < mNumTemps; i++) {
sb.append(mRecentTemps[i]).append(',');
if (type == TEMPERATURE_OVERHEAT_WARNING) {
for (int i = 0; i < mWarningNumTemps; i++) {
sb.append(mRecentSkinTemps[i]).append(',');
}
} else {
for (int i = 0; i < mAlarmNumTemps; i++) {
sb.append(mRecentAlarmTemps[i]).append(',');
}
}
Slog.i(TAG, sb.toString());
}
@@ -466,16 +586,20 @@ public class PowerUI extends SystemUI {
* Calculates and logs min, max, and average
* {@link HardwarePropertiesManager#DEVICE_TEMPERATURE_SKIN} over the past
* {@link #TEMPERATURE_LOGGING_INTERVAL}.
* @param type TEMPERATURE_OVERHEAT_WARNING Send generic notification to notify user
* TEMPERATURE_OVERHEAT_ALARM Popup emergency Dialog for user
*/
private void logTemperatureStats() {
if (mNextLogTime > System.currentTimeMillis() && mNumTemps != MAX_RECENT_TEMPS) {
private void logTemperatureStats(int type) {
int numTemp = type == TEMPERATURE_OVERHEAT_ALARM ? mAlarmNumTemps : mWarningNumTemps;
if (mNextLogTime > System.currentTimeMillis() && numTemp != MAX_RECENT_TEMPS) {
return;
}
if (mNumTemps > 0) {
float sum = mRecentTemps[0], min = mRecentTemps[0], max = mRecentTemps[0];
for (int i = 1; i < mNumTemps; i++) {
float temp = mRecentTemps[i];
float[] recentTemps =
type == TEMPERATURE_OVERHEAT_ALARM ? mRecentAlarmTemps : mRecentSkinTemps;
if (numTemp > 0) {
float sum = recentTemps[0], min = recentTemps[0], max = recentTemps[0];
for (int i = 1; i < numTemp; i++) {
float temp = recentTemps[i];
sum += temp;
if (temp > max) {
max = temp;
@@ -485,14 +609,22 @@ public class PowerUI extends SystemUI {
}
}
float avg = sum / mNumTemps;
Slog.i(TAG, "avg=" + avg + ",min=" + min + ",max=" + max);
MetricsLogger.histogram(mContext, "device_skin_temp_avg", (int) avg);
MetricsLogger.histogram(mContext, "device_skin_temp_min", (int) min);
MetricsLogger.histogram(mContext, "device_skin_temp_max", (int) max);
float avg = sum / numTemp;
Slog.i(TAG, "Type=" + type + ",avg=" + avg + ",min=" + min + ",max=" + max);
String t = type == TEMPERATURE_OVERHEAT_WARNING ? "skin" : "alarm";
MetricsLogger.histogram(mContext,
String.format(Locale.ENGLISH, "device_%1$s_temp_avg", t), (int) avg);
MetricsLogger.histogram(mContext,
String.format(Locale.ENGLISH, "device_%1$s_temp_min", t), (int) min);
MetricsLogger.histogram(mContext,
String.format(Locale.ENGLISH, "device_%1$s_temp_max", t), (int) max);
}
setNextLogTime();
mNumTemps = 0;
if (type == TEMPERATURE_OVERHEAT_ALARM) {
mAlarmNumTemps = 0;
} else {
mWarningNumTemps = 0;
}
}
private void setNextLogTime() {
@@ -525,8 +657,10 @@ public class PowerUI extends SystemUI {
Settings.Global.LOW_BATTERY_SOUND_TIMEOUT, 0));
pw.print("bucket: ");
pw.println(Integer.toString(findBatteryLevelBucket(mBatteryLevel)));
pw.print("mThresholdTemp=");
pw.println(Float.toString(mThresholdTemp));
pw.print("mThresholdWarningTemp=");
pw.println(Float.toString(mThresholdWarningTemp));
pw.print("mThresholdAlarmTemp=");
pw.println(Float.toString(mThresholdAlarmTemp));
pw.print("mNextLogTime=");
pw.println(Long.toString(mNextLogTime));
mWarnings.dump(pw);
@@ -534,18 +668,41 @@ public class PowerUI extends SystemUI {
public interface WarningsUI {
void update(int batteryLevel, int bucket, long screenOffTime);
void updateEstimate(Estimate estimate);
void updateThresholds(long lowThreshold, long severeThreshold);
void dismissLowBatteryWarning();
void showLowBatteryWarning(boolean playSound);
void dismissInvalidChargerWarning();
void showInvalidChargerWarning();
void updateLowBatteryWarning();
boolean isInvalidChargerWarningShowing();
void dismissHighTemperatureWarning();
void showHighTemperatureWarning();
/**
* PowerUI detect thermal overheat, notify to popup alert dialog strongly.
* Alarm with beep sound with un-dismissible dialog until device cool down below threshold,
* then popup another dismissible dialog to user.
*
* @param overheat whether device temperature over threshold
* @param beepSound should beep sound once overheat
*/
void notifyHighTemperatureAlarm(boolean overheat, boolean beepSound);
void showThermalShutdownWarning();
void dump(PrintWriter pw);
void userSwitched();
}
@@ -554,9 +711,9 @@ public class PowerUI extends SystemUI {
@Override public void notifyThrottling(boolean isThrottling, Temperature temp) {
// Trigger an update of the temperature warning. Only one
// callback can be enabled at a time, so remove any existing
// callback; updateTemperatureWarning will schedule another one.
// callback; updateTemperature will schedule another one.
mHandler.removeCallbacks(mUpdateTempCallback);
updateTemperatureWarning();
updateTemperature();
}
}
}

View File

@@ -53,6 +53,8 @@ public class Events {
public static final int EVENT_TOUCH_LEVEL_DONE = 16; // (stream|int) (level|bool)
public static final int EVENT_ZEN_CONFIG_CHANGED = 17; // (allow/disallow|string)
public static final int EVENT_RINGER_TOGGLE = 18; // (ringer_mode)
public static final int EVENT_SHOW_OVERHEAT_ALARM = 19; // (reason|int) (keyguard|bool)
public static final int EVENT_DISMISS_OVERHEAT_ALARM = 20; // (reason|int) (keyguard|bool)
private static final String[] EVENT_TAGS = {
"show_dialog",
@@ -73,7 +75,9 @@ public class Events {
"mute_changed",
"touch_level_done",
"zen_mode_config_changed",
"ringer_toggle"
"ringer_toggle",
"show_overheat_alarm",
"dismiss_overheat_alarm"
};
public static final int DISMISS_REASON_UNKNOWN = 0;
@@ -100,10 +104,12 @@ public class Events {
public static final int SHOW_REASON_UNKNOWN = 0;
public static final int SHOW_REASON_VOLUME_CHANGED = 1;
public static final int SHOW_REASON_REMOTE_VOLUME_CHANGED = 2;
public static final int SHOW_REASON_OVERHEAD_ALARM_CHANGED = 3;
public static final String[] SHOW_REASONS = {
"unknown",
"volume_changed",
"remote_volume_changed"
"remote_volume_changed",
"overheat_alarm_changed"
};
public static final int ICON_STATE_UNKNOWN = 0;
@@ -181,6 +187,19 @@ public class Events {
case EVENT_SUPPRESSOR_CHANGED:
sb.append(list[0]).append(' ').append(list[1]);
break;
case EVENT_SHOW_OVERHEAT_ALARM:
MetricsLogger.visible(context, MetricsEvent.POWER_OVERHEAT_ALARM);
MetricsLogger.histogram(context, "show_overheat_alarm",
(Boolean) list[1] ? 1 : 0);
sb.append(SHOW_REASONS[(Integer) list[0]]).append(" keyguard=").append(list[1]);
break;
case EVENT_DISMISS_OVERHEAT_ALARM:
MetricsLogger.hidden(context, MetricsEvent.POWER_OVERHEAT_ALARM);
MetricsLogger.histogram(context, "dismiss_overheat_alarm",
(Boolean) list[1] ? 1 : 0);
sb.append(DISMISS_REASONS[(Integer) list[0]]).append(" keyguard=").append(
list[1]);
break;
default:
sb.append(Arrays.asList(list));
break;

View File

@@ -51,6 +51,7 @@
<uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW" />
<uses-permission android:name="android.permission.NETWORK_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.VIBRATE" />
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />

View File

@@ -0,0 +1,87 @@
/*
* 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.systemui.power;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import com.android.systemui.SysuiTestCase;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@SmallTest
public class OverheatAlarmControllerTest extends SysuiTestCase{
OverheatAlarmController mOverheatAlarmController;
OverheatAlarmController mSpyOverheatAlarmController;
@Before
public void setUp() {
mOverheatAlarmController = OverheatAlarmController.getInstance(mContext);
mSpyOverheatAlarmController = spy(mOverheatAlarmController);
}
@After
public void tearDown() {
mSpyOverheatAlarmController.stopAlarm();
}
@Test
public void testGetInstance() {
assertThat(mOverheatAlarmController).isNotNull();
}
@Test
public void testStartAlarm_PlaySound() {
mSpyOverheatAlarmController.startAlarm(mContext);
verify(mSpyOverheatAlarmController).playSound(mContext);
}
@Test
public void testStartAlarm_InitVibrate() {
mSpyOverheatAlarmController.startAlarm(mContext);
verify(mSpyOverheatAlarmController).startVibrate();
}
@Test
public void testStartAlarm_StartVibrate() {
mSpyOverheatAlarmController.startAlarm(mContext);
verify(mSpyOverheatAlarmController).performVibrate();
}
@Test
public void testStopAlarm_StopPlayer() {
mSpyOverheatAlarmController.stopAlarm();
verify(mSpyOverheatAlarmController).stopPlayer();
}
@Test
public void testStopAlarm_StopVibrate() {
mSpyOverheatAlarmController.stopAlarm();
verify(mSpyOverheatAlarmController).stopVibrate();
}
}

View File

@@ -0,0 +1,130 @@
/*
* 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.systemui.power;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.os.SystemClock;
import android.support.test.filters.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.WindowManager;
import com.android.systemui.SysuiTestCase;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidTestingRunner.class)
@RunWithLooper(setAsMainLooper = true)
@SmallTest
public class OverheatAlarmDialogTest extends SysuiTestCase {
private OverheatAlarmDialog mDialog, mSpyDialog;
@Before
public void setup() {
mDialog = new OverheatAlarmDialog(mContext);
mSpyDialog = spy(mDialog);
}
@After
public void tearDown() {
mSpyDialog = mDialog = null;
}
@Test
public void testFlagShowForAllUsers() {
assertThat((mDialog.getWindow().getAttributes().privateFlags
& WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS) != 0).isTrue();
}
@Test
public void testFlagShowWhenLocked() {
assertThat((mDialog.getWindow().getAttributes().flags
& WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED) != 0).isTrue();
}
@Test
public void testFlagTurnScreenOn() {
assertThat((mDialog.getWindow().getAttributes().flags
& WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON) != 0).isTrue();
}
@Test
public void testFlagKeepScreenOn() {
assertThat((mDialog.getWindow().getAttributes().flags
& WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) != 0).isTrue();
}
@Test
public void testTouchOutsideDialog_NotifyAlarmBeepSoundIntent_ShouldStopBeepSound() {
final long currentTime = SystemClock.uptimeMillis();
mSpyDialog.show();
MotionEvent ev = createMotionEvent(MotionEvent.ACTION_DOWN, currentTime, 0, 0);
mSpyDialog.dispatchTouchEvent(ev);
verify(mSpyDialog, atLeastOnce()).notifyAlarmBeepSoundChange();
mSpyDialog.dismiss();
}
@Test
public void testPressBackKey_NotifyAlarmBeepSoundIntent_ShouldStopBeepSound() {
mSpyDialog.show();
KeyEvent ev = new KeyEvent(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK, 0,
0);
mSpyDialog.dispatchKeyEvent(ev);
verify(mSpyDialog, atLeastOnce()).notifyAlarmBeepSoundChange();
mSpyDialog.dismiss();
}
@Test
public void testPressVolumeUp_NotifyAlarmBeepSoundIntent_ShouldStopBeepSound() {
mSpyDialog.show();
KeyEvent ev = new KeyEvent(0, 0, MotionEvent.ACTION_DOWN,
KeyEvent.KEYCODE_VOLUME_UP, 0, 0);
mSpyDialog.dispatchKeyEvent(ev);
verify(mSpyDialog, atLeastOnce()).notifyAlarmBeepSoundChange();
mSpyDialog.dismiss();
}
@Test
public void testPressVolumeDown_NotifyAlarmBeepSoundIntent_ShouldStopBeepSound() {
mSpyDialog.show();
KeyEvent ev = new KeyEvent(0, 0, MotionEvent.ACTION_DOWN,
KeyEvent.KEYCODE_VOLUME_DOWN, 0, 0);
mSpyDialog.dispatchKeyEvent(ev);
verify(mSpyDialog, atLeastOnce()).notifyAlarmBeepSoundChange();
mSpyDialog.dismiss();
}
private MotionEvent createMotionEvent(int action, long eventTime, float x, float y) {
return MotionEvent.obtain(0, eventTime, action, x, y, 0);
}
}

View File

@@ -16,9 +16,8 @@
package com.android.systemui.power;
import static android.test.MoreAsserts.assertNotEqual;
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
@@ -26,6 +25,8 @@ import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -38,7 +39,7 @@ import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.util.NotificationChannels;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -51,13 +52,22 @@ public class PowerNotificationWarningsTest extends SysuiTestCase {
public static final String FORMATTED_45M = "0h 45m";
public static final String FORMATTED_HOUR = "1h 0m";
private final NotificationManager mMockNotificationManager = mock(NotificationManager.class);
private PowerNotificationWarnings mPowerNotificationWarnings;
private PowerNotificationWarnings mPowerNotificationWarnings, mSpyPowerNotificationWarnings;
@Before
public void setUp() throws Exception {
// Test Instance.
mContext.addMockSystemService(NotificationManager.class, mMockNotificationManager);
mPowerNotificationWarnings = new PowerNotificationWarnings(mContext);
mSpyPowerNotificationWarnings = spy(mPowerNotificationWarnings);
}
@After
public void tearDown() throws Exception {
if (mSpyPowerNotificationWarnings.mOverheatAlarmDialog != null) {
mSpyPowerNotificationWarnings.mOverheatAlarmDialog.dismiss();
mSpyPowerNotificationWarnings.mOverheatAlarmDialog = null;
}
}
@Test
@@ -151,4 +161,146 @@ public class PowerNotificationWarningsTest extends SysuiTestCase {
verify(mMockNotificationManager, times(1)).cancelAsUser(anyString(),
eq(SystemMessage.NOTE_THERMAL_SHUTDOWN), any());
}
@Test
public void testSetOverheatAlarmDialog_Overheat_ShouldShowing() {
final boolean overheat = true;
final boolean shouldBeepSound = false;
mContext.getMainThreadHandler().post(
() -> mSpyPowerNotificationWarnings.notifyHighTemperatureAlarm(overheat,
shouldBeepSound));
waitForIdleSync();
verify(mSpyPowerNotificationWarnings, times(1)).setOverheatAlarmDialogShowing(overheat);
verify(mSpyPowerNotificationWarnings, times(1)).setAlarmShouldSound(shouldBeepSound);
}
@Test
public void testSetOverheatAlarmDialog_Overheat_ShouldShowingWithBeepSound() {
final boolean overheat = true;
final boolean shouldBeepSound = true;
mContext.getMainThreadHandler().post(
() -> mSpyPowerNotificationWarnings.notifyHighTemperatureAlarm(overheat,
shouldBeepSound));
waitForIdleSync();
verify(mSpyPowerNotificationWarnings, times(1)).setOverheatAlarmDialogShowing(overheat);
verify(mSpyPowerNotificationWarnings, times(1)).setAlarmShouldSound(shouldBeepSound);
}
@Test
public void testSetOverheatAlarmDialog_NotOverheat_ShouldNotShowing() {
final boolean overheat = false;
final boolean shouldBeepSound = false;
mContext.getMainThreadHandler().post(
() -> mSpyPowerNotificationWarnings.notifyHighTemperatureAlarm(overheat,
shouldBeepSound));
waitForIdleSync();
verify(mSpyPowerNotificationWarnings, never()).setOverheatAlarmDialogShowing(overheat);
verify(mSpyPowerNotificationWarnings, never()).setAlarmShouldSound(shouldBeepSound);
}
@Test
public void testSetOverheatAlarmDialog_NotOverheat_ShouldNotAlarmBeepSound() {
final boolean overheat = false;
final boolean configBeepSound = true;
mContext.getMainThreadHandler().post(
() -> mSpyPowerNotificationWarnings.notifyHighTemperatureAlarm(overheat,
configBeepSound));
waitForIdleSync();
verify(mSpyPowerNotificationWarnings, never()).setOverheatAlarmDialogShowing(overheat);
verify(mSpyPowerNotificationWarnings, never()).setAlarmShouldSound(configBeepSound);
}
@Test
public void testSetAlarmShouldSound_OverheatDrop_ShouldNotSound() {
final boolean overheat = true;
final boolean shouldBeepSound = true;
mContext.getMainThreadHandler().post(
() -> mSpyPowerNotificationWarnings.notifyHighTemperatureAlarm(overheat,
shouldBeepSound));
waitForIdleSync();
// First time overheat, show overheat alarm dialog with alarm beep sound
verify(mSpyPowerNotificationWarnings, times(1)).setOverheatAlarmDialogShowing(overheat);
verify(mSpyPowerNotificationWarnings, times(1)).setAlarmShouldSound(shouldBeepSound);
// After disconnected cable or temperature drop
mContext.getMainThreadHandler().post(
() -> mSpyPowerNotificationWarnings.notifyHighTemperatureAlarm(!overheat,
!shouldBeepSound));
waitForIdleSync();
verify(mSpyPowerNotificationWarnings, times(1)).setOverheatAlarmDialogShowing(!overheat);
verify(mSpyPowerNotificationWarnings, times(1)).setAlarmShouldSound(!shouldBeepSound);
}
@Test
public void testSetAlarmShouldSound_Overheat_Twice_ShouldShowOverheatDialogAgain() {
final boolean overheat = true;
final boolean shouldBeepSound = true;
// First time overheat, show mAlarmDialog and alarm beep sound
mContext.getMainThreadHandler().post(
() -> mSpyPowerNotificationWarnings.notifyHighTemperatureAlarm(overheat,
shouldBeepSound));
waitForIdleSync();
verify(mSpyPowerNotificationWarnings, times(1)).setOverheatAlarmDialogShowing(overheat);
verify(mSpyPowerNotificationWarnings, times(1)).setAlarmShouldSound(shouldBeepSound);
// After disconnected cable or temperature drop, stop beep sound
mContext.getMainThreadHandler().post(
() -> mSpyPowerNotificationWarnings.notifyHighTemperatureAlarm(!overheat,
!shouldBeepSound));
waitForIdleSync();
verify(mSpyPowerNotificationWarnings, times(1)).setOverheatAlarmDialogShowing(!overheat);
verify(mSpyPowerNotificationWarnings, times(1)).setAlarmShouldSound(!shouldBeepSound);
// Overheat again, ensure the previous dialog do not auto-dismiss
mContext.getMainThreadHandler().post(
() -> mSpyPowerNotificationWarnings.notifyHighTemperatureAlarm(overheat,
shouldBeepSound));
waitForIdleSync();
verify(mSpyPowerNotificationWarnings, times(1)).setOverheatAlarmDialogShowing(overheat);
verify(mSpyPowerNotificationWarnings, times(1)).setAlarmShouldSound(shouldBeepSound);
}
@Test
public void testOverheatAlarmDialogShowing() {
final boolean overheat = true;
final boolean shouldBeepSound = false;
mContext.getMainThreadHandler().post(
() -> mSpyPowerNotificationWarnings.notifyHighTemperatureAlarm(overheat,
shouldBeepSound));
waitForIdleSync();
assertThat(mSpyPowerNotificationWarnings.mOverheatAlarmDialog).isNotNull();
}
@Test
public void testOverheatAlarmDialogShowingWithBeepSound() {
final boolean overheat = true;
final boolean shouldBeepSound = true;
mContext.getMainThreadHandler().post(
() -> mSpyPowerNotificationWarnings.notifyHighTemperatureAlarm(overheat,
shouldBeepSound));
waitForIdleSync();
assertThat(mSpyPowerNotificationWarnings.mOverheatAlarmDialog).isNotNull();
}
@Test
public void testOverheatAlarmDialogNotShowing() {
final boolean overheat = false;
final boolean shouldBeepSound = false;
mContext.getMainThreadHandler().post(
() -> mSpyPowerNotificationWarnings.notifyHighTemperatureAlarm(overheat,
shouldBeepSound));
waitForIdleSync();
assertThat(mSpyPowerNotificationWarnings.mOverheatAlarmDialog).isNull();
}
@Test
public void testOverheatAlarmDialogNotShowingWithBeepSound() {
final boolean overheat = false;
final boolean shouldBeepSound = true;
mContext.getMainThreadHandler().post(
() -> mSpyPowerNotificationWarnings.notifyHighTemperatureAlarm(overheat,
shouldBeepSound));
waitForIdleSync();
assertThat(mSpyPowerNotificationWarnings.mOverheatAlarmDialog).isNull();
}
}

View File

@@ -17,6 +17,7 @@ package com.android.systemui.power;
import static android.os.HardwarePropertiesManager.DEVICE_TEMPERATURE_SKIN;
import static android.os.HardwarePropertiesManager.TEMPERATURE_CURRENT;
import static android.os.HardwarePropertiesManager.TEMPERATURE_SHUTDOWN;
import static android.os.HardwarePropertiesManager.TEMPERATURE_THROTTLING;
import static android.provider.Settings.Global.SHOW_TEMPERATURE_WARNING;
import static junit.framework.Assert.assertFalse;
@@ -69,6 +70,7 @@ public class PowerUITest extends SysuiTestCase {
private static final long ABOVE_CHARGE_CYCLE_THRESHOLD = Duration.ofHours(8).toMillis();
private static final int OLD_BATTERY_LEVEL_NINE = 9;
private static final int OLD_BATTERY_LEVEL_10 = 10;
private static final int DEFAULT_OVERHEAT_ALARM_THRESHOLD = 58;
private HardwarePropertiesManager mHardProps;
private WarningsUI mMockWarnings;
private PowerUI mPowerUI;
@@ -86,6 +88,7 @@ public class PowerUITest extends SysuiTestCase {
mContext.addMockSystemService(Context.HARDWARE_PROPERTIES_SERVICE, mHardProps);
mContext.addMockSystemService(Context.POWER_SERVICE, mPowerManager);
setUnderThreshold();
createPowerUi();
}
@@ -153,10 +156,94 @@ public class PowerUITest extends SysuiTestCase {
verify(mMockWarnings, never()).showHighTemperatureWarning();
setCurrentTemp(56); // Above threshold.
mPowerUI.updateTemperatureWarning();
mPowerUI.updateTemperature();
verify(mMockWarnings).showHighTemperatureWarning();
}
@Test
public void testNoConfig_noAlarms() {
setOverThreshold();
final Boolean overheat = false;
final Boolean shouldBeepSound = false;
TestableResources resources = mContext.getOrCreateTestableResources();
resources.addOverride(R.integer.config_showTemperatureWarning, 0);
resources.addOverride(R.integer.config_alarmTemperature, 55);
resources.addOverride(R.bool.config_alarmTemperatureBeepSound, shouldBeepSound);
mPowerUI.start();
verify(mMockWarnings, never()).notifyHighTemperatureAlarm(overheat, shouldBeepSound);
}
@Test
public void testConfig_noAlarms() {
setUnderThreshold();
final Boolean overheat = false;
final Boolean shouldBeepSound = false;
TestableResources resources = mContext.getOrCreateTestableResources();
resources.addOverride(R.integer.config_showTemperatureAlarm, 1);
resources.addOverride(R.integer.config_alarmTemperature, 58);
resources.addOverride(R.bool.config_alarmTemperatureBeepSound, shouldBeepSound);
mPowerUI.start();
verify(mMockWarnings, never()).notifyHighTemperatureAlarm(overheat, shouldBeepSound);
}
@Test
public void testConfig_alarms() {
setOverThreshold();
final Boolean overheat = true;
final Boolean shouldBeepSound = false;
TestableResources resources = mContext.getOrCreateTestableResources();
resources.addOverride(R.integer.config_showTemperatureAlarm, 1);
resources.addOverride(R.integer.config_alarmTemperature, 58);
resources.addOverride(R.bool.config_alarmTemperatureBeepSound, shouldBeepSound);
mPowerUI.start();
verify(mMockWarnings).notifyHighTemperatureAlarm(overheat, shouldBeepSound);
}
@Test
public void testConfig_alarmsWithBeepSound() {
setOverThreshold();
final Boolean overheat = true;
final Boolean shouldBeepSound = true;
TestableResources resources = mContext.getOrCreateTestableResources();
resources.addOverride(R.integer.config_showTemperatureAlarm, 1);
resources.addOverride(R.integer.config_alarmTemperature, 58);
resources.addOverride(R.bool.config_alarmTemperatureBeepSound, shouldBeepSound);
mPowerUI.start();
verify(mMockWarnings).notifyHighTemperatureAlarm(overheat, shouldBeepSound);
}
@Test
public void testHardPropsThrottlingThreshold_alarms() {
setThrottlingThreshold(DEFAULT_OVERHEAT_ALARM_THRESHOLD);
setOverThreshold();
final Boolean overheat = true;
final Boolean shouldBeepSound = false;
TestableResources resources = mContext.getOrCreateTestableResources();
resources.addOverride(R.integer.config_showTemperatureAlarm, 1);
resources.addOverride(R.bool.config_alarmTemperatureBeepSound, shouldBeepSound);
mPowerUI.start();
verify(mMockWarnings).notifyHighTemperatureAlarm(overheat, shouldBeepSound);
}
@Test
public void testHardPropsThrottlingThreshold_noAlarms() {
setThrottlingThreshold(DEFAULT_OVERHEAT_ALARM_THRESHOLD);
setUnderThreshold();
final Boolean overheat = false;
final Boolean shouldBeepSound = false;
TestableResources resources = mContext.getOrCreateTestableResources();
resources.addOverride(R.integer.config_showTemperatureAlarm, 1);
resources.addOverride(R.bool.config_alarmTemperatureBeepSound, shouldBeepSound);
mPowerUI.start();
verify(mMockWarnings, never()).notifyHighTemperatureAlarm(overheat, shouldBeepSound);
}
@Test
public void testShouldShowLowBatteryWarning_showHybridOnly_overrideThresholdHigh_returnsNoShow() {
when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
@@ -495,7 +582,12 @@ public class PowerUITest extends SysuiTestCase {
private void setCurrentTemp(float temp) {
when(mHardProps.getDeviceTemperatures(DEVICE_TEMPERATURE_SKIN, TEMPERATURE_CURRENT))
.thenReturn(new float[] { temp });
.thenReturn(new float[] { temp, temp });
}
private void setThrottlingThreshold(float temp) {
when(mHardProps.getDeviceTemperatures(DEVICE_TEMPERATURE_SKIN, TEMPERATURE_THROTTLING))
.thenReturn(new float[] { temp, temp });
}
private void setOverThreshold() {

View File

@@ -6117,6 +6117,11 @@ message MetricsEvent {
// OS: P
FIELD_AUTOFILL_SESSION_ID = 1456;
// FIELD: Device USB overheat alarm trigger.
// CATEGORY: GLOBAL_SYSTEM_UI
// OS: P
POWER_OVERHEAT_ALARM = 1457;
// NOTIFICATION_SINCE_INTERRUPTION_MILLIS added to P
// NOTIFICATION_INTERRUPTION added to P