Add "device is muted/vibrate, click to turn on" conditions

Bug: 76022431
Test: robotests
Change-Id: I89b71f99fa5ef866028912183ad04b053213bb0d
This commit is contained in:
Fan Zhang
2018-03-26 17:37:47 -07:00
parent 74ac3b1f3a
commit 8878a957f8
13 changed files with 609 additions and 5 deletions

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:viewportHeight="23.4"
android:viewportWidth="23.3"
android:width="24dp"
android:tint="?android:attr/colorControlNormal">
<group
android:translateX="-0.85"
android:translateY="-0.5">
<path
android:fillColor="#FFF"
android:pathData="M20.73,19.46l-0.6,-0.6c0,0 0,0 0,0L5.54,4.26c-0.35,-0.35 -0.92,-0.35 -1.27,0c-0.35,0.35 -0.35,0.92 0,1.27l2.4,2.4C6.25,8.85 6,9.88 6,11v5l-2.15,2.15c-0.19,0.2 -0.19,0.51 0.01,0.71C3.95,18.95 4.07,19 4.2,19h13.53l1.73,1.73c0.35,0.35 0.92,0.35 1.27,0C21.09,20.38 21.09,19.81 20.73,19.46z" />
<path
android:fillColor="#FFF"
android:pathData="M18,11c0,-3.07 -1.64,-5.64 -4.5,-6.32V4c0,-0.83 -0.67,-1.5 -1.5,-1.5S10.5,3.17 10.5,4v0.68C9.87,4.83 9.31,5.08 8.8,5.4l9.2,9.2V11z" />
<path
android:fillColor="#FFF"
android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4C10,21.1 10.9,22 12,22z" />
</group>
</vector>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0"
android:width="24dp"
android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="#FFFFFF"
android:pathData="M1,15c0.55,0 1,-0.45 1,-1v-4c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v4c0,0.55 0.45,1 1,1zM4,17c0.55,0 1,-0.45 1,-1L5,8c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v8c0,0.55 0.45,1 1,1zM22,10v4c0,0.55 0.45,1 1,1s1,-0.45 1,-1v-4c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1zM20,17c0.55,0 1,-0.45 1,-1L21,8c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v8c0,0.55 0.45,1 1,1zM16.5,3h-9C6.67,3 6,3.67 6,4.5v15c0,0.83 0.67,1.5 1.5,1.5h9c0.83,0 1.5,-0.67 1.5,-1.5v-15c0,-0.83 -0.67,-1.5 -1.5,-1.5zM16,19L8,19L8,5h8v14z" />
</vector>

View File

@@ -0,0 +1,83 @@
/*
* 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.dashboard.conditional;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.provider.Settings;
import com.android.settings.R;
public abstract class AbnormalRingerConditionBase extends Condition {
private final IntentFilter mFilter;
protected final AudioManager mAudioManager;
private final RingerModeChangeReceiver mReceiver;
AbnormalRingerConditionBase(ConditionManager manager) {
super(manager);
mAudioManager =
(AudioManager) mManager.getContext().getSystemService(Context.AUDIO_SERVICE);
mReceiver = new RingerModeChangeReceiver(this);
mFilter = new IntentFilter(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
manager.getContext().registerReceiver(mReceiver, mFilter);
}
@Override
public CharSequence[] getActions() {
return new CharSequence[] {
mManager.getContext().getText(R.string.condition_device_muted_action_turn_on_sound)
};
}
@Override
public void onPrimaryClick() {
mManager.getContext().startActivity(
new Intent(Settings.ACTION_SOUND_SETTINGS)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
@Override
public void onActionClick(int index) {
mAudioManager.setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL);
mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 1, 0 /* flags */);
refreshState();
}
static class RingerModeChangeReceiver extends BroadcastReceiver {
private final AbnormalRingerConditionBase mCondition;
public RingerModeChangeReceiver(AbnormalRingerConditionBase condition) {
mCondition = condition;
}
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) {
mCondition.refreshState();
}
}
}
}

View File

@@ -20,7 +20,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.net.ConnectivityManager;
import android.provider.Settings;
import android.util.Log;

View File

@@ -24,6 +24,7 @@ import android.util.Xml;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnPause;
import com.android.settingslib.core.lifecycle.events.OnResume;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -153,6 +154,8 @@ public class ConditionManager implements LifecycleObserver, OnResume, OnPause {
addIfMissing(BackgroundDataCondition.class, conditions);
addIfMissing(WorkModeCondition.class, conditions);
addIfMissing(NightDisplayCondition.class, conditions);
addIfMissing(RingerMutedCondition.class, conditions);
addIfMissing(RingerVibrateCondition.class, conditions);
Collections.sort(conditions, CONDITION_COMPARATOR);
}
@@ -183,6 +186,10 @@ public class ConditionManager implements LifecycleObserver, OnResume, OnPause {
return new WorkModeCondition(this);
} else if (NightDisplayCondition.class == clz) {
return new NightDisplayCondition(this);
} else if (RingerMutedCondition.class == clz) {
return new RingerMutedCondition(this);
} else if (RingerVibrateCondition.class == clz) {
return new RingerVibrateCondition(this);
}
Log.e(TAG, "unknown condition class: " + clz.getSimpleName());
return null;

View File

@@ -0,0 +1,63 @@
/*
* 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.dashboard.conditional;
import android.app.NotificationManager;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.provider.Settings;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
public class RingerMutedCondition extends AbnormalRingerConditionBase {
private final NotificationManager mNotificationManager;
RingerMutedCondition(ConditionManager manager) {
super(manager);
mNotificationManager = mManager.getContext().getSystemService(NotificationManager.class);
}
@Override
public void refreshState() {
int zen = mNotificationManager.getZenMode();
boolean zenModeEnabled = zen != Settings.Global.ZEN_MODE_OFF;
boolean isSilent = mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_SILENT;
setActive(isSilent && !zenModeEnabled);
}
@Override
public int getMetricsConstant() {
return MetricsProto.MetricsEvent.SETTINGS_CONDITION_DEVICE_MUTED;
}
@Override
public Drawable getIcon() {
return mManager.getContext().getDrawable(R.drawable.ic_volume_ringer_mute);
}
@Override
public CharSequence getTitle() {
return mManager.getContext().getText(R.string.condition_device_muted_title);
}
@Override
public CharSequence getSummary() {
return mManager.getContext().getText(R.string.condition_device_muted_summary);
}
}

View File

@@ -0,0 +1,55 @@
/*
* 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.dashboard.conditional;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
public class RingerVibrateCondition extends AbnormalRingerConditionBase {
RingerVibrateCondition(ConditionManager manager) {
super(manager);
}
@Override
public void refreshState() {
setActive(mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_VIBRATE);
}
@Override
public int getMetricsConstant() {
return MetricsProto.MetricsEvent.SETTINGS_CONDITION_DEVICE_VIBRATE;
}
@Override
public Drawable getIcon() {
return mManager.getContext().getDrawable(R.drawable.ic_volume_ringer_vibrate);
}
@Override
public CharSequence getTitle() {
return mManager.getContext().getText(R.string.condition_device_vibrate_title);
}
@Override
public CharSequence getSummary() {
return mManager.getContext().getText(R.string.condition_device_vibrate_summary);
}
}

View File

@@ -22,16 +22,14 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.media.AudioManager;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Vibrator;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.notification.VolumeSeekBarPreference.Callback;
import com.android.settingslib.core.lifecycle.Lifecycle;
@@ -99,7 +97,7 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr
@Override
public int getMuteIcon() {
return com.android.internal.R.drawable.ic_audio_ring_notif_mute;
return R.drawable.ic_volume_ringer_vibrate;
}
private void updateRingerMode() {

View File

@@ -0,0 +1,98 @@
/*
* 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.dashboard.conditional;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
@RunWith(SettingsRobolectricTestRunner.class)
public class AbnormalRingerConditionBaseTest {
@Mock
private ConditionManager mConditionManager;
private Context mContext;
private TestCondition mCondition;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
when(mConditionManager.getContext()).thenReturn(mContext);
mCondition = new TestCondition(mConditionManager);
}
@Test
public void newInstance_shouldMonitorRingerStateChangeBroadcast() {
final Intent broadcast1 = new Intent("foo.bar.action");
final Intent broadcast2 = new Intent(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
mContext.sendBroadcast(broadcast1);
assertThat(mCondition.mRefreshCalled).isFalse();
mContext.sendBroadcast(broadcast2);
assertThat(mCondition.mRefreshCalled).isTrue();
}
private static class TestCondition extends AbnormalRingerConditionBase {
private boolean mRefreshCalled;
TestCondition(ConditionManager manager) {
super(manager);
}
@Override
public void refreshState() {
mRefreshCalled = true;
}
@Override
public int getMetricsConstant() {
return 0;
}
@Override
public Drawable getIcon() {
return null;
}
@Override
public CharSequence getTitle() {
return null;
}
@Override
public CharSequence getSummary() {
return null;
}
}
}

View File

@@ -0,0 +1,96 @@
/*
* 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.dashboard.conditional;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.NotificationManager;
import android.content.Context;
import android.media.AudioManager;
import android.provider.Settings;
import com.android.settings.R;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.shadow.ShadowAudioManager;
import com.android.settings.testutils.shadow.ShadowNotificationManager;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(shadows = {ShadowNotificationManager.class, ShadowAudioManager.class})
public class RingerMutedConditionTest {
private static final String TAG = "RingerMutedConditionTest";
@Mock
private ConditionManager mConditionManager;
private Context mContext;
private ShadowNotificationManager mNotificationManager;
private ShadowAudioManager mAudioManager;
private RingerMutedCondition mCondition;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mAudioManager = Shadow.extract(mContext.getSystemService(Context.AUDIO_SERVICE));
mNotificationManager = Shadow.extract(
mContext.getSystemService(NotificationManager.class));
when(mConditionManager.getContext()).thenReturn(mContext);
mCondition = spy(new RingerMutedCondition(mConditionManager));
}
@Test
public void verifyText() {
assertThat(mCondition.getTitle()).isEqualTo(
mContext.getText(R.string.condition_device_muted_title));
assertThat(mCondition.getSummary()).isEqualTo(
mContext.getText(R.string.condition_device_muted_summary));
assertThat(mCondition.getActions()[0]).isEqualTo(
mContext.getText(R.string.condition_device_muted_action_turn_on_sound));
}
@Test
public void refreshState_zenModeOn_shouldNotActivate() {
mAudioManager.setRingerModeInternal(AudioManager.RINGER_MODE_SILENT);
mNotificationManager.setZenMode(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS, null, TAG);
mCondition.refreshState();
verify(mCondition).setActive(false);
}
@Test
public void refreshState_zenModeOff_shouldActivate() {
mAudioManager.setRingerModeInternal(AudioManager.RINGER_MODE_SILENT);
mNotificationManager.setZenMode(Settings.Global.ZEN_MODE_OFF, null, TAG);
mCondition.refreshState();
verify(mCondition).setActive(true);
}
}

View File

@@ -0,0 +1,60 @@
/*
* 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.dashboard.conditional;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.when;
import android.content.Context;
import com.android.settings.R;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
@RunWith(SettingsRobolectricTestRunner.class)
public class RingerVibrateConditionTest {
@Mock
private ConditionManager mConditionManager;
private Context mContext;
private RingerVibrateCondition mCondition;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
when(mConditionManager.getContext()).thenReturn(mContext);
mCondition = new RingerVibrateCondition(mConditionManager);
}
@Test
public void verifyText() {
assertThat(mCondition.getTitle()).isEqualTo(
mContext.getText(R.string.condition_device_vibrate_title));
assertThat(mCondition.getSummary()).isEqualTo(
mContext.getText(R.string.condition_device_vibrate_summary));
assertThat(mCondition.getActions()[0]).isEqualTo(
mContext.getText(R.string.condition_device_muted_action_turn_on_sound));
}
}

View File

@@ -0,0 +1,38 @@
/*
* 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.testutils.shadow;
import android.media.AudioManager;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
@Implements(AudioManager.class)
public class ShadowAudioManager {
private int mRingerMode;
@Implementation
public void setRingerModeInternal(int mode) {
mRingerMode = mode;
}
@Implementation
private int getRingerModeInternal() {
return mRingerMode;
}
}

View File

@@ -0,0 +1,41 @@
/*
* 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.testutils.shadow;
import android.app.NotificationManager;
import android.net.Uri;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
@Implements(NotificationManager.class)
public class ShadowNotificationManager {
private int mZenMode;
@Implementation
public void setZenMode(int mode, Uri conditionId, String reason) {
mZenMode = mode;
}
@Implementation
public int getZenMode() {
return mZenMode;
}
}