/* * Copyright (C) 2019 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.network.telephony; import static android.app.slice.Slice.EXTRA_TOGGLE_STATE; import android.annotation.ColorInt; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import androidx.core.graphics.drawable.IconCompat; import androidx.slice.Slice; import androidx.slice.builders.ListBuilder; import androidx.slice.builders.SliceAction; import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.network.MobileDataContentObserver; import com.android.settings.network.SubscriptionUtil; import com.android.settings.slices.CustomSliceRegistry; import com.android.settings.slices.CustomSliceable; import com.android.settings.slices.SliceBackgroundWorker; import com.android.settingslib.WirelessUtils; import com.google.common.annotations.VisibleForTesting; import java.io.IOException; import java.util.List; /** * Custom {@link Slice} for Mobile Data. *

* We make a custom slice instead of using {@link MobileDataPreferenceController} because the * pref controller is generalized across any carrier, and thus does not control a specific * subscription. We attempt to reuse any telephony-specific code from the preference controller. * *

* */ public class MobileDataSlice implements CustomSliceable { private final Context mContext; private final SubscriptionManager mSubscriptionManager; private final TelephonyManager mTelephonyManager; public MobileDataSlice(Context context) { mContext = context; mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class); mTelephonyManager = mContext.getSystemService(TelephonyManager.class); } @Override public Slice getSlice() { final IconCompat icon = IconCompat.createWithResource(mContext, R.drawable.ic_network_cell); final String title = mContext.getText(R.string.mobile_data_settings_title).toString(); @ColorInt final int color = Utils.getColorAccentDefaultColor(mContext); // Return null until we can show a disabled-action Slice, blaming Airplane mode. if (isAirplaneModeEnabled()) { return null; } // Return null until we can show a disabled-action Slice. if (!isMobileDataAvailable()) { return null; } final CharSequence summary = getSummary(); final PendingIntent toggleAction = getBroadcastIntent(mContext); final PendingIntent primaryAction = getPrimaryAction(); final SliceAction primarySliceAction = SliceAction.createDeeplink(primaryAction, icon, ListBuilder.ICON_IMAGE, title); final SliceAction toggleSliceAction = SliceAction.createToggle(toggleAction, null /* actionTitle */, isMobileDataEnabled()); final ListBuilder.RowBuilder rowBuilder = new ListBuilder.RowBuilder() .setTitle(title) .addEndItem(toggleSliceAction) .setPrimaryAction(primarySliceAction); if (!Utils.isSettingsIntelligence(mContext)) { rowBuilder.setSubtitle(summary); } final ListBuilder listBuilder = new ListBuilder(mContext, getUri(), ListBuilder.INFINITY) .setAccentColor(color) .addRow(rowBuilder); return listBuilder.build(); } @Override public Uri getUri() { return CustomSliceRegistry.MOBILE_DATA_SLICE_URI; } @Override public void onNotifyChange(Intent intent) { final boolean newState = intent.getBooleanExtra(EXTRA_TOGGLE_STATE, isMobileDataEnabled()); final int defaultSubId = getDefaultSubscriptionId(mSubscriptionManager); if (defaultSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { return; // No subscription - do nothing. } MobileNetworkUtils.setMobileDataEnabled(mContext, defaultSubId, newState, false /* disableOtherSubscriptions */); // Do not notifyChange on Uri. The service takes longer to update the current value than it // does for the Slice to check the current value again. Let {@link WifiScanWorker} // handle it. } @Override public IntentFilter getIntentFilter() { final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); return filter; } @Override public Intent getIntent() { return new Intent(mContext, MobileNetworkActivity.class); } @Override public int getSliceHighlightMenuRes() { return R.string.menu_key_network; } @Override public Class getBackgroundWorkerClass() { return MobileDataWorker.class; } protected static int getDefaultSubscriptionId(SubscriptionManager subscriptionManager) { final SubscriptionInfo defaultSubscription = subscriptionManager.getActiveSubscriptionInfo( subscriptionManager.getDefaultDataSubscriptionId()); if (defaultSubscription == null) { return SubscriptionManager.INVALID_SUBSCRIPTION_ID; // No default subscription } return defaultSubscription.getSubscriptionId(); } private CharSequence getSummary() { final SubscriptionInfo defaultSubscription = mSubscriptionManager.getActiveSubscriptionInfo( mSubscriptionManager.getDefaultDataSubscriptionId()); if (defaultSubscription == null) { return null; // no summary text } return SubscriptionUtil.getUniqueSubscriptionDisplayName(defaultSubscription, mContext); } private PendingIntent getPrimaryAction() { final Intent intent = getIntent(); return PendingIntent.getActivity(mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE); } /** * @return {@code true} when mobile data is not supported by the current device. */ private boolean isMobileDataAvailable() { final List subInfoList = SubscriptionUtil.getSelectableSubscriptionInfoList(mContext); return !(subInfoList == null || subInfoList.isEmpty()); } @VisibleForTesting boolean isAirplaneModeEnabled() { return WirelessUtils.isAirplaneModeOn(mContext); } @VisibleForTesting boolean isMobileDataEnabled() { if (mTelephonyManager == null) { return false; } return mTelephonyManager.isDataEnabled(); } /** * Listener for mobile data state changes. * *

* Listen to individual subscription changes since there is no framework broadcast. * * This worker registers a ContentObserver in the background and updates the MobileData * Slice when the value changes. */ public static class MobileDataWorker extends SliceBackgroundWorker { DataContentObserver mMobileDataObserver; public MobileDataWorker(Context context, Uri uri) { super(context, uri); final Handler handler = new Handler(Looper.getMainLooper()); mMobileDataObserver = new DataContentObserver(handler, this); } @Override protected void onSlicePinned() { final SubscriptionManager subscriptionManager = getContext().getSystemService(SubscriptionManager.class); mMobileDataObserver.register(getContext(), getDefaultSubscriptionId(subscriptionManager)); } @Override protected void onSliceUnpinned() { mMobileDataObserver.unRegister(getContext()); } @Override public void close() throws IOException { mMobileDataObserver = null; } public void updateSlice() { notifySliceChange(); } public class DataContentObserver extends ContentObserver { private final MobileDataWorker mSliceBackgroundWorker; public DataContentObserver(Handler handler, MobileDataWorker backgroundWorker) { super(handler); mSliceBackgroundWorker = backgroundWorker; } @Override public void onChange(boolean selfChange) { mSliceBackgroundWorker.updateSlice(); } public void register(Context context, int subId) { final Uri uri = MobileDataContentObserver.getObservableUri(context, subId); context.getContentResolver().registerContentObserver(uri, false, this); } public void unRegister(Context context) { context.getContentResolver().unregisterContentObserver(this); } } } }