From 45c5b77da5cf3f470deaf3e5e86b635544158030 Mon Sep 17 00:00:00 2001 From: Christian Oder Date: Tue, 26 Nov 2019 23:35:01 +0100 Subject: SystemUI: Introduce DataSwitchTile * Based on OnePlus' OxygenOS tile, reworked to work with AOSP toggling without requirements on proprietary telephony-ext features. * QS icons are designed and made by Andrew Fluck. Co-authored-by: idoybh Co-authored-by: jhonboy121 Change-Id: Ie2e280c07f24f9da6b4ee218b72501a2713ce429 --- data/etc/com.android.systemui.xml | 1 + packages/SystemUI/AndroidManifest.xml | 3 + .../SystemUI/res/drawable/ic_qs_data_switch_1.xml | 35 +++ .../SystemUI/res/drawable/ic_qs_data_switch_2.xml | 35 +++ packages/SystemUI/res/values/config.xml | 2 +- packages/SystemUI/res/values/ice_strings.xml | 7 + .../systemui/qs/tileimpl/QSFactoryImpl.java | 8 +- .../android/systemui/qs/tiles/DataSwitchTile.java | 251 +++++++++++++++++++++ 8 files changed, 340 insertions(+), 2 deletions(-) create mode 100644 packages/SystemUI/res/drawable/ic_qs_data_switch_1.xml create mode 100644 packages/SystemUI/res/drawable/ic_qs_data_switch_2.xml create mode 100644 packages/SystemUI/src/com/android/systemui/qs/tiles/DataSwitchTile.java diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml index f2a33de008d6..437ad2fa035e 100644 --- a/data/etc/com.android.systemui.xml +++ b/data/etc/com.android.systemui.xml @@ -60,6 +60,7 @@ + diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 1e3b45eada21..41b320caf600 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -294,6 +294,9 @@ + + + + + + + + + + diff --git a/packages/SystemUI/res/drawable/ic_qs_data_switch_2.xml b/packages/SystemUI/res/drawable/ic_qs_data_switch_2.xml new file mode 100644 index 000000000000..cd0b057174d9 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_qs_data_switch_2.xml @@ -0,0 +1,35 @@ + + + + + + + + diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 9226ca873156..18dd1dce7727 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -107,7 +107,7 @@ - internet,wifi,cell,bt,flashlight,dnd,alarm,airplane,nfc,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,ambient_display,aod,caffeine,heads_up + internet,wifi,cell,bt,flashlight,dnd,alarm,airplane,nfc,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,ambient_display,aod,caffeine,heads_up,dataswitch diff --git a/packages/SystemUI/res/values/ice_strings.xml b/packages/SystemUI/res/values/ice_strings.xml index 36c1e3a2592c..d4d8b24834fd 100644 --- a/packages/SystemUI/res/values/ice_strings.xml +++ b/packages/SystemUI/res/values/ice_strings.xml @@ -38,4 +38,11 @@ Heads up on. Heads up turned off. Heads up turned on. + + + Switch data card + Currently no SIM card is inserted + Currently only one SIM card is inserted + Switch data card to SIM 1. + Switch data card to SIM 2. diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java index 7a18353792ee..1301708ac648 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java @@ -55,6 +55,7 @@ import com.android.systemui.qs.tiles.UiModeNightTile; import com.android.systemui.qs.tiles.UserTile; import com.android.systemui.qs.tiles.WifiTile; import com.android.systemui.qs.tiles.WorkModeTile; +import com.android.systemui.qs.tiles.DataSwitchTile; import com.android.systemui.util.leak.GarbageMonitor; import javax.inject.Inject; @@ -98,6 +99,7 @@ public class QSFactoryImpl implements QSFactory { private final Provider mAODTileProvider; private final Provider mCaffeineTileProvider; private final Provider mHeadsUpTileProvider; + private final Provider mDataSwitchTileProvider; private final Lazy mQsHostLazy; private final Provider mCustomTileBuilderProvider; @@ -136,7 +138,8 @@ public class QSFactoryImpl implements QSFactory { Provider ambientDisplayTileProvider, Provider aodTileProvider, Provider caffeineTileProvider, - Provider headsUpTileProvider) { + Provider headsUpTileProvider, + Provider dataSwitchTileProvider) { mQsHostLazy = qsHostLazy; mCustomTileBuilderProvider = customTileBuilderProvider; @@ -171,6 +174,7 @@ public class QSFactoryImpl implements QSFactory { mAODTileProvider = aodTileProvider; mCaffeineTileProvider = caffeineTileProvider; mHeadsUpTileProvider = headsUpTileProvider; + mDataSwitchTileProvider = dataSwitchTileProvider; } public QSTile createTile(String tileSpec) { @@ -246,6 +250,8 @@ public class QSFactoryImpl implements QSFactory { return mCaffeineTileProvider.get(); case "heads_up": return mHeadsUpTileProvider.get(); + case "dataswitch": + return mDataSwitchTileProvider.get(); } // Custom tiles diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSwitchTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSwitchTile.java new file mode 100644 index 000000000000..1850b1aa5e38 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSwitchTile.java @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use mHost 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.qs.tiles; + +import android.annotation.Nullable; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Handler; +import android.os.Looper; +import android.os.SystemProperties; +import android.provider.Settings; +import android.telephony.PhoneStateListener; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; +import android.widget.Toast; + +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.telephony.IccCardConstants; +import com.android.internal.telephony.TelephonyIntents; +import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.R; +import com.android.systemui.plugins.qs.QSTile.BooleanState; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon; + +import java.util.concurrent.Executors; +import java.util.List; + +import javax.inject.Inject; + +public class DataSwitchTile extends QSTileImpl { + private boolean mCanSwitch = true; + private boolean mRegistered; + private int mSimCount; + + private final Intent mLongClickIntent; + private final String mTileLabel; + private final BroadcastReceiver mSimReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (DEBUG) Log.d(TAG, "mSimReceiver:onReceive"); + refreshState(); + } + }; + private final PhoneStateListener mPhoneStateListener; + private final SubscriptionManager mSubscriptionManager; + private final TelephonyManager mTelephonyManager; + + @Inject + public DataSwitchTile( + QSHost host, + @Background Looper backgroundLooper, + @Main Handler mainHandler, + FalsingManager falsingManager, + MetricsLogger metricsLogger, + StatusBarStateController statusBarStateController, + ActivityStarter activityStarter, + QSLogger qsLogger + ) { + super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + statusBarStateController, activityStarter, qsLogger); + mLongClickIntent = new Intent(Settings.ACTION_NETWORK_OPERATOR_SETTINGS); + mTileLabel = mContext.getString(R.string.qs_data_switch_label); + mSubscriptionManager = SubscriptionManager.from(mContext); + mTelephonyManager = TelephonyManager.from(mContext); + mPhoneStateListener = new PhoneStateListener() { + @Override + public void onCallStateChanged(int state, String arg1) { + mCanSwitch = mTelephonyManager.getCallState() == 0; + refreshState(); + } + }; + } + + @Override + public boolean isAvailable() { + int count = TelephonyManager.getDefault().getPhoneCount(); + if (DEBUG) Log.d(TAG, "phoneCount: " + count); + return count >= 2; + } + + @Override + public BooleanState newTileState() { + final BooleanState state = new BooleanState(); + state.label = mContext.getString(R.string.qs_data_switch_label); + return state; + } + + @Override + public void handleSetListening(boolean listening) { + if (listening) { + if (!mRegistered) { + final IntentFilter filter = new IntentFilter(TelephonyIntents.ACTION_SIM_STATE_CHANGED); + mContext.registerReceiver(mSimReceiver, filter); + mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); + mRegistered = true; + } + refreshState(); + } else if (mRegistered) { + mContext.unregisterReceiver(mSimReceiver); + mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE); + mRegistered = false; + } + } + + private void updateSimCount() { + String simState = SystemProperties.get("gsm.sim.state"); + if (DEBUG) Log.d(TAG, "DataSwitchTile:updateSimCount:simState=" + simState); + mSimCount = 0; + try { + final String[] sims = TextUtils.split(simState, ","); + for (String sim : sims) { + if (!sim.isEmpty() + && !sim.equalsIgnoreCase(IccCardConstants.INTENT_VALUE_ICC_ABSENT) + && !sim.equalsIgnoreCase(IccCardConstants.INTENT_VALUE_ICC_NOT_READY)) { + mSimCount++; + } + } + } catch (NullPointerException e) { + Log.e(TAG, "Error on parsing sim state " + e.getMessage()); + } + if (DEBUG) Log.d(TAG, "DataSwitchTile:updateSimCount:mSimCount=" + mSimCount); + } + + @Override + protected void handleClick(@Nullable View view) { + if (!mCanSwitch) { + if (DEBUG) Log.d(TAG, "Call state=" + mTelephonyManager.getCallState()); + } else if (mSimCount == 0) { + if (DEBUG) Log.d(TAG, "handleClick:no sim card"); + Toast.makeText(mContext, R.string.qs_data_switch_toast_0, + Toast.LENGTH_LONG).show(); + } else if (mSimCount == 1) { + if (DEBUG) Log.d(TAG, "handleClick:only one sim card"); + Toast.makeText(mContext, R.string.qs_data_switch_toast_1, + Toast.LENGTH_LONG).show(); + } else { + Executors.newSingleThreadExecutor().execute(() -> { + toggleMobileDataEnabled(); + refreshState(); + }); + } + } + + @Override + public Intent getLongClickIntent() { + return mLongClickIntent; + } + + @Override + public CharSequence getTileLabel() { + return mTileLabel; + } + + @Override + protected void handleUpdateState(BooleanState state, Object arg) { + boolean activeSIMZero; + if (arg == null) { + int defaultPhoneId = mSubscriptionManager.getDefaultDataPhoneId(); + if (DEBUG) Log.d(TAG, "default data phone id=" + defaultPhoneId); + activeSIMZero = defaultPhoneId == 0; + } else { + activeSIMZero = (Boolean) arg; + } + updateSimCount(); + state.value = mSimCount == 2; + if (mSimCount == 1 || mSimCount == 2) { + state.icon = ResourceIcon.get(activeSIMZero + ? R.drawable.ic_qs_data_switch_1 + : R.drawable.ic_qs_data_switch_2); + } else { + state.icon = ResourceIcon.get(R.drawable.ic_qs_data_switch_1); + } + if (mSimCount < 2 || !mCanSwitch) { + state.state = 0; + if (!mCanSwitch && DEBUG) Log.d(TAG, "call state isn't idle, set to unavailable."); + } else { + state.state = state.value ? 2 : 1; + } + + state.contentDescription = + mContext.getString(activeSIMZero + ? R.string.qs_data_switch_changed_1 + : R.string.qs_data_switch_changed_2); + } + + @Override + public int getMetricsCategory() { + return MetricsEvent.ICE; + } + + @Override + protected String composeChangeAnnouncement() { + return mContext.getString(mState.value + ? R.string.qs_data_switch_changed_1 + : R.string.qs_data_switch_changed_2); + } + + /** + * Set whether to enable data for {@code subId}, also whether to disable data for other + * subscription + */ + private void toggleMobileDataEnabled() { + // Get opposite slot 2 ^ 3 = 1, 1 ^ 3 = 2 + int subId = SubscriptionManager.getDefaultDataSubscriptionId() ^ 3; + final TelephonyManager telephonyManager = + mTelephonyManager.createForSubscriptionId(subId); + telephonyManager.setDataEnabled(true); + mSubscriptionManager.setDefaultDataSubId(subId); + if (DEBUG) Log.d(TAG, "Enabled subID: " + subId); + + final List subInfoList = + mSubscriptionManager.getActiveSubscriptionInfoList(true); + if (subInfoList != null) { + // We never disable mobile data for opportunistic subscriptions. + subInfoList.stream() + .filter(subInfo -> !subInfo.isOpportunistic()) + .map(subInfo -> subInfo.getSubscriptionId()) + .filter(id -> id != subId) + .forEach(id -> { + mTelephonyManager.createForSubscriptionId(id).setDataEnabled(false); + if (DEBUG) Log.d(TAG, "Disabled subID: " + id); + }); + } + } +} -- cgit v1.2.3