Create a main switch preference widget.

Based on our ux spec, we create this widget to
let everyone can follow up our spec easily.

Bug: 175181773
Test: Run robotest and apply this widget in Settings and see the ui
Change-Id: I4ea5f38c44aacfeb2c8dbe4a7d31159712f95441
This commit is contained in:
Stanley Wang
2020-12-09 17:12:21 +08:00
parent 69f35b55c2
commit 5728b5be2f
23 changed files with 1072 additions and 0 deletions

View File

@@ -43,6 +43,7 @@ java_defaults {
"SettingsLibSearchWidget",
"SettingsLibSettingsSpinner",
"SettingsLibLayoutPreference",
"SettingsLibMainSwitchPreference",
"SettingsLibActionButtonsPreference",
"SettingsLibEntityHeaderWidgets",
"SettingsLibBarChartPreference",

View File

@@ -0,0 +1,14 @@
android_library {
name: "SettingsLibMainSwitchPreference",
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
static_libs: [
"androidx.preference_preference",
"SettingsLibRestrictedLockUtils",
],
sdk_version: "system_current",
min_sdk_version: "21",
}

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2020 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.settingslib.widget">
<uses-sdk android:minSdkVersion="21" />
</manifest>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2020 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.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval" >
<size android:height="24dp" android:width="24dp" />
<solid android:color="@color/thumb_off" />
<stroke android:width="4dp" android:color="@android:color/transparent" />
</shape>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2020 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.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval" >
<size android:height="24dp" android:width="24dp" />
<solid android:color="?android:attr/colorAccent" />
<stroke android:width="4dp" android:color="@android:color/transparent" />
</shape>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2020 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/thumb_on" android:state_checked="true" />
<item android:drawable="@drawable/thumb_off" android:state_checked="false" />
</selector>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2020 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.
-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/track_off_background" />
<item
android:width="13.33dp"
android:height="1.67dp"
android:left="19dp"
android:gravity="center"
android:drawable="@drawable/track_off_indicator" />
</layer-list>

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2020 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:width="48dp"
android:height="24dp"
android:viewportWidth="48"
android:viewportHeight="24">
<group>
<clip-path
android:pathData="M12 0H36C42.6274 0 48 5.37258 48 12C48 18.6274 42.6274 24 36 24H12C5.37258 24 0 18.6274 0 12C0 5.37258 5.37258 0 12 0Z" />
<path
android:pathData="M0 0V24H48V0"
android:fillColor="@color/track_off" />
</group>
</vector>

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2020 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:width="13.33dp"
android:height="1.67dp"
android:viewportWidth="13.33"
android:viewportHeight="1.67">
<group>
<clip-path
android:pathData="M0 0H13.3333V1.66667H0V0Z" />
<path
android:pathData="M0 0V1.66667H13.3333V0"
android:fillColor="@android:color/white" />
</group>
</vector>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2020 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.
-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/track_on_background" />
<item
android:width="13.19dp"
android:height="10.06dp"
android:gravity="center"
android:right="19dp"
android:drawable="@drawable/track_on_indicator" />
</layer-list>

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2020 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:width="48dp"
android:height="24dp"
android:viewportWidth="48"
android:viewportHeight="24">
<group>
<clip-path
android:pathData="M12 0H36C42.6274 0 48 5.37258 48 12C48 18.6274 42.6274 24 36 24H12C5.37258 24 0 18.6274 0 12C0 5.37258 5.37258 0 12 0Z" />
<path
android:pathData="M0 0V24H48V0"
android:fillColor="@color/track_on" />
</group>
</vector>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2020 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:width="13.19dp"
android:height="10.06dp"
android:viewportWidth="13.19"
android:viewportHeight="10.06">
<group>
<path
android:pathData="M4.75012 8.12738L1.62262 4.99988L0.557617 6.05738L4.75012 10.2499L13.7501 1.24988L12.6926 0.192383L4.75012 8.12738Z"
android:fillColor="@android:color/white" />
</group>
</vector>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2020 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/track_on" android:state_checked="true" />
<item android:drawable="@drawable/track_off" android:state_checked="false" />
</selector>

View File

@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2020 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:layout_height="wrap_content"
android:layout_width="match_parent"
android:background="@android:color/transparent"
android:orientation="vertical">
<LinearLayout
android:id="@+id/frame"
android:minHeight="@dimen/min_switch_bar_height"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:background="@android:color/transparent"
android:paddingLeft="@dimen/switchbar_margin_start"
android:paddingRight="@dimen/switchbar_margin_end">
<TextView
android:id="@+id/switch_text"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:maxLines="2"
android:ellipsize="end"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textAlignment="viewStart"
style="@style/MainSwitchText" />
<ImageView
android:id="@+id/restricted_icon"
android:layout_width="@dimen/restricted_icon_size"
android:layout_height="@dimen/restricted_icon_size"
android:tint="?android:attr/colorAccent"
android:theme="@android:style/Theme.Material"
android:layout_gravity="center_vertical"
android:layout_marginEnd="@dimen/restricted_icon_margin_end"
android:src="@*android:drawable/ic_info"
android:visibility="gone" />
<Switch
android:id="@android:id/switch_widget"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:track="@drawable/track_selector"
android:thumb="@drawable/thumb_selector"
android:theme="@style/Settings.MainSwitch"/>
</LinearLayout>
<View
android:id="@+id/below_divider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/listDivider" />
</LinearLayout>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2020 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:layout_height="wrap_content"
android:layout_width="match_parent" >
<com.android.settingslib.widget.MainSwitchBar
android:id="@+id/main_switch_bar"
android:visibility="gone"
android:layout_height="wrap_content"
android:layout_width="match_parent" />
</LinearLayout>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2020 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.
-->
<resources>
<color name="title_text_color">@*android:color/primary_text_dark</color>
<color name="thumb_off">#BFFFFFFF</color>
<color name="track_on">@*android:color/material_grey_600</color>
<color name="track_off">@*android:color/material_grey_600</color>
</resources>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2020 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.
-->
<resources>
<color name="background_color">@android:color/transparent</color>
<color name="title_text_color">@*android:color/primary_text_light</color>
<color name="thumb_off">#BFFFFFFF</color>
<color name="track_on">@*android:color/accent_device_default_dark</color>
<color name="track_off">@*android:color/material_grey_600</color>
</resources>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2020 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.
-->
<resources>
<!-- Size of layout margin left -->
<dimen name="switchbar_margin_start">26dp</dimen>
<!-- Size of layout margin right -->
<dimen name="switchbar_margin_end">24dp</dimen>
<!-- Minimum width of switch -->
<dimen name="min_switch_width">48dp</dimen>
<!-- Minimum width of switch bar -->
<dimen name="min_switch_bar_height">72dp</dimen>
<!-- Restricted icon size in switch bar -->
<dimen name="restricted_icon_size">@*android:dimen/config_restrictedIconSize</dimen>
<!-- Restricted icon in switch bar -->
<dimen name="restricted_icon_margin_end">16dp</dimen>
</resources>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2020 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.
-->
<resources>
<style name="MainSwitchText">
<item name="android:textSize">20sp</item>
<item name="android:textColor">@color/title_text_color</item>
</style>
<style name="Settings.MainSwitch" parent="@android:style/Widget.Material.CompoundButton.Switch">
<item name="android:switchMinWidth">@dimen/min_switch_width</item>
</style>
</resources>

View File

@@ -0,0 +1,283 @@
/*
* Copyright (C) 2020 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.settingslib.widget;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Switch;
import android.widget.TextView;
import com.android.settingslib.RestrictedLockUtils;
import java.util.ArrayList;
import java.util.List;
/**
* MainSwitchBar is a View with a customized Switch.
* This component is used as the main switch of the page
* to enable or disable the prefereces on the page.
*/
public class MainSwitchBar extends LinearLayout implements CompoundButton.OnCheckedChangeListener {
private final List<OnMainSwitchChangeListener> mSwitchChangeListeners = new ArrayList<>();
private View mAboveDivider;
private View mBelowDivider;
private TextView mTextView;
private ImageView mRestrictedIcon;
private Switch mSwitch;
private RestrictedLockUtils.EnforcedAdmin mEnforcedAdmin;
private boolean mDisabledByAdmin;
public MainSwitchBar(Context context) {
this(context, null);
}
public MainSwitchBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MainSwitchBar(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public MainSwitchBar(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
LayoutInflater.from(context).inflate(R.layout.main_switch_bar, this);
setFocusable(true);
setClickable(true);
mTextView = (TextView) findViewById(R.id.switch_text);
mSwitch = (Switch) findViewById(android.R.id.switch_widget);
addOnSwitchChangeListener((switchView, isChecked) -> setChecked(isChecked));
mRestrictedIcon = findViewById(R.id.restricted_icon);
mRestrictedIcon.setOnClickListener((View v) -> {
if (mDisabledByAdmin) {
RestrictedLockUtils.sendShowAdminSupportDetailsIntent(context, mEnforcedAdmin);
onRestrictedIconClick();
}
});
setChecked(mSwitch.isChecked());
}
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
propagateChecked(isChecked);
}
@Override
public boolean performClick() {
return getDelegatingView().performClick();
}
/**
* Update the switch status
*/
public void setChecked(boolean checked) {
if (mSwitch != null) {
mSwitch.setChecked(checked);
}
}
/**
* Set the title text
*/
public void setTitle(String text) {
if (mTextView != null) {
mTextView.setText(text);
}
}
/**
* Show the MainSwitchBar
*/
public void show() {
if (!isShowing()) {
setVisibility(View.VISIBLE);
mSwitch.setOnCheckedChangeListener(this);
}
}
/**
* Hide the MainSwitchBar
*/
public void hide() {
if (isShowing()) {
setVisibility(View.GONE);
mSwitch.setOnCheckedChangeListener(null);
}
}
/**
* Return the displaying status of MainSwitchBar
*/
public boolean isShowing() {
return (getVisibility() == View.VISIBLE);
}
/**
* Adds a listener for switch changes
*/
public void addOnSwitchChangeListener(OnMainSwitchChangeListener listener) {
if (!mSwitchChangeListeners.contains(listener)) {
mSwitchChangeListeners.add(listener);
}
}
/**
* Remove a listener for switch changes
*/
public void removeOnSwitchChangeListener(OnMainSwitchChangeListener listener) {
if (mSwitchChangeListeners.contains(listener)) {
mSwitchChangeListeners.remove(listener);
}
}
/**
* If admin is not null, disables the text and switch but keeps the view clickable.
* Otherwise, calls setEnabled which will enables the entire view including
* the text and switch.
*/
public void setDisabledByAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
mEnforcedAdmin = admin;
if (admin != null) {
super.setEnabled(true);
mDisabledByAdmin = true;
mTextView.setEnabled(false);
mSwitch.setEnabled(false);
mSwitch.setVisibility(View.GONE);
mRestrictedIcon.setVisibility(View.VISIBLE);
} else {
mDisabledByAdmin = false;
mSwitch.setVisibility(View.VISIBLE);
mRestrictedIcon.setVisibility(View.GONE);
setEnabled(true);
}
}
/**
* Enable or disable the text and switch.
*/
public void setEnabled(boolean enabled) {
if (enabled && mDisabledByAdmin) {
setDisabledByAdmin(null);
return;
}
super.setEnabled(enabled);
mTextView.setEnabled(enabled);
mSwitch.setEnabled(enabled);
}
/**
* Called by the restricted icon clicked.
*/
protected void onRestrictedIconClick() {
}
private View getDelegatingView() {
return mDisabledByAdmin ? mRestrictedIcon : mSwitch;
}
private void propagateChecked(boolean isChecked) {
final int count = mSwitchChangeListeners.size();
for (int n = 0; n < count; n++) {
mSwitchChangeListeners.get(n).onSwitchChanged(mSwitch, isChecked);
}
}
static class SavedState extends BaseSavedState {
boolean mChecked;
boolean mVisible;
SavedState(Parcelable superState) {
super(superState);
}
/**
* Constructor called from {@link #CREATOR}
*/
private SavedState(Parcel in) {
super(in);
mChecked = (Boolean) in.readValue(null);
mVisible = (Boolean) in.readValue(null);
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeValue(mChecked);
out.writeValue(mVisible);
}
@Override
public String toString() {
return "MainSwitchBar.SavedState{"
+ Integer.toHexString(System.identityHashCode(this))
+ " checked=" + mChecked
+ " visible=" + mVisible + "}";
}
public static final Parcelable.Creator<SavedState> CREATOR =
new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.mChecked = mSwitch.isChecked();
ss.mVisible = isShowing();
return ss;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
mSwitch.setChecked(ss.mChecked);
setChecked(ss.mChecked);
setVisibility(ss.mVisible ? View.VISIBLE : View.GONE);
mSwitch.setOnCheckedChangeListener(ss.mVisible ? this : null);
requestLayout();
}
}

View File

@@ -0,0 +1,146 @@
/*
* Copyright (C) 2020 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.settingslib.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.Switch;
import androidx.preference.PreferenceViewHolder;
import androidx.preference.TwoStatePreference;
import com.android.settingslib.RestrictedLockUtils;
import java.util.ArrayList;
import java.util.List;
/**
* MainSwitchPreference is a Preference with a customized Switch.
* This component is used as the main switch of the page
* to enable or disable the prefereces on the page.
*/
public class MainSwitchPreference extends TwoStatePreference {
private final List<OnMainSwitchChangeListener> mSwitchChangeListeners = new ArrayList<>();
private MainSwitchBar mMainSwitchBar;
private Switch mSwitch;
private String mTitle;
private RestrictedLockUtils.EnforcedAdmin mEnforcedAdmin;
public MainSwitchPreference(Context context) {
super(context);
init();
}
public MainSwitchPreference(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MainSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public MainSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
holder.setDividerAllowedAbove(true);
holder.setDividerAllowedBelow(false);
mMainSwitchBar = (MainSwitchBar) holder.findViewById(R.id.main_switch_bar);
updateStatus(isChecked());
registerListenerToSwitchBar();
}
private void init() {
setLayoutResource(R.layout.main_switch_layout);
}
/**
* Set the preference title text
*/
public void setTitle(String text) {
mTitle = text;
if (mMainSwitchBar != null) {
mMainSwitchBar.setTitle(mTitle);
}
}
/**
* Update the switch status of preference
*/
public void updateStatus(boolean checked) {
setChecked(checked);
if (mMainSwitchBar != null) {
mMainSwitchBar.setChecked(checked);
mMainSwitchBar.setTitle(mTitle);
mMainSwitchBar.setDisabledByAdmin(mEnforcedAdmin);
mMainSwitchBar.show();
}
}
/**
* Adds a listener for switch changes
*/
public void addOnSwitchChangeListener(OnMainSwitchChangeListener listener) {
if (mMainSwitchBar == null) {
mSwitchChangeListeners.add(listener);
} else {
mMainSwitchBar.addOnSwitchChangeListener(listener);
}
}
/**
* Remove a listener for switch changes
*/
public void removeOnSwitchChangeListener(OnMainSwitchChangeListener listener) {
if (mMainSwitchBar == null) {
mSwitchChangeListeners.remove(listener);
} else {
mMainSwitchBar.removeOnSwitchChangeListener(listener);
}
}
/**
* If admin is not null, disables the text and switch but keeps the view clickable.
* Otherwise, calls setEnabled which will enables the entire view including
* the text and switch.
*/
public void setDisabledByAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
mEnforcedAdmin = admin;
if (mMainSwitchBar != null) {
mMainSwitchBar.setDisabledByAdmin(mEnforcedAdmin);
}
}
private void registerListenerToSwitchBar() {
for (OnMainSwitchChangeListener listener : mSwitchChangeListeners) {
mMainSwitchBar.addOnSwitchChangeListener(listener);
}
mSwitchChangeListeners.clear();
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2020 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.settingslib.widget;
import android.widget.Switch;
/**
* Called when the checked state of the Switch has changed.
*/
public interface OnMainSwitchChangeListener {
/**
* @param switchView The Switch view whose state has changed.
* @param isChecked The new checked state of switchView.
*/
void onSwitchChanged(Switch switchView, boolean isChecked);
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright (C) 2020 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.settingslib.widget;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.view.View;
import android.widget.TextView;
import androidx.preference.PreferenceViewHolder;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
public class MainSwitchPreferenceTest {
private Context mContext;
private View mRootView;
private PreferenceViewHolder mHolder;
private MainSwitchPreference mPreference;
@Before
public void setUp() {
mContext = RuntimeEnvironment.application;
mRootView = View.inflate(mContext, R.layout.main_switch_layout, null /* parent */);
mHolder = PreferenceViewHolder.createInstanceForTests(mRootView);
mPreference = new MainSwitchPreference(mContext);
}
@Test
public void setTitleText_shouldUpdateTitle() {
final String defaultOnText = "Test title";
mPreference.onBindViewHolder(mHolder);
mPreference.setTitle(defaultOnText);
mPreference.updateStatus(true /* checked */);
assertThat(((TextView) mRootView.findViewById(R.id.switch_text)).getText())
.isEqualTo(defaultOnText);
}
@Test
public void shouldAllowDividerBelow() {
mPreference.onBindViewHolder(mHolder);
View divider = mRootView.findViewById(R.id.below_divider);
assertThat(divider.getVisibility()).isEqualTo(View.VISIBLE);
}
@Test
public void updateStatus_shouldMatchTheStatus() {
mPreference.onBindViewHolder(mHolder);
mPreference.updateStatus(true);
assertThat(mPreference.isChecked()).isTrue();
}
}