blob: e54ea0ad2ea724fff7103ee7bb9cad8a7a119a08 [file] [log] [blame]
package com.android.clockwork.common;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
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.os.SystemClock;
import android.provider.Settings;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Cell Only Mode is a temporary mode which forces cellular to be the only
* enabled radio.
*
* This is used for eSIM activation scenarios where the device must attach
* and connect to the cell network in order to get a SIM OTA.
*
* CellOnlyMode automatically disables itself after a configured time or
* if none is configured, after a default time has elapsed.
*
* To use this feature from a system app:
* 1. Optionally set Settings.System.clockwork_cell_only_mode_duration_seconds
* 2. Write "1" to Settings.System.clockwork_cell_only_mode
*/
public class CellOnlyMode {
private static final String TAG = "WearConnectivityService";
public interface Listener {
void onCellOnlyModeChanged(boolean enabled);
}
/**
* valid values for this key are 0 and 1
*/
static final String CELL_ONLY_MODE_SETTING_KEY = "clockwork_cell_only_mode";
static final String CELL_ONLY_MODE_DURATION_SECONDS_SETTING_KEY =
"clockwork_cell_only_mode_duration_seconds";
static final String ACTION_EXIT_CELL_ONLY_MODE =
"com.android.clockwork.connectivity.action.ACTION_EXIT_CELL_ONLY_MODE";
/**
* If no duration is specified in the setting, use this value.
*/
static final long DEFAULT_CELL_ONLY_MODE_DURATION_MS = TimeUnit.MINUTES.toMillis(10);
/**
* If duration is specified in the setting exceeds this value, use this value instead.
*/
static final long MAX_CELL_ONLY_MODE_DURATION_MS = TimeUnit.MINUTES.toMillis(30);
/** Do not delay at all when turning the radio back off. */
static final long EXIT_CELL_ONLY_LINGER_DURATION_MS = 0;
private final Context mContext;
private final AlarmManager mAlarmManager;
private final ContentResolver mContentResolver;
private final SettingsObserver mSettingsObserver;
private final Set<Listener> mListeners;
private boolean mCellOnlyModeEnabled;
@VisibleForTesting final PendingIntent exitCellOnlyModeIntent;
@VisibleForTesting
BroadcastReceiver exitCellOnlyModeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (ACTION_EXIT_CELL_ONLY_MODE.equals(intent.getAction())) {
Log.d(TAG, "Alarm received. Exiting cell only mode.");
resetCellOnlyMode();
for (Listener listener : mListeners) {
listener.onCellOnlyModeChanged(false);
}
}
}
};
public CellOnlyMode(Context context,
ContentResolver contentResolver,
AlarmManager alarmManager) {
mContext = context;
mAlarmManager = alarmManager;
mContentResolver = contentResolver;
mCellOnlyModeEnabled = false;
mListeners = new HashSet<>();
mSettingsObserver = new SettingsObserver(new Handler(Looper.getMainLooper()));
mContentResolver.registerContentObserver(
Settings.System.getUriFor(CELL_ONLY_MODE_SETTING_KEY), false, mSettingsObserver);
exitCellOnlyModeIntent =
PendingIntent.getBroadcast(
context,
0,
new Intent(ACTION_EXIT_CELL_ONLY_MODE),
PendingIntent.FLAG_IMMUTABLE);
}
@VisibleForTesting
SettingsObserver getSettingsObserver() {
return mSettingsObserver;
}
/**
* If CellOnlyMode was enabled but not reset before the device power cycled, ensure that
* it is turned off immediately.
*/
public void onBootCompleted() {
mContext.registerReceiver(exitCellOnlyModeReceiver,
new IntentFilter(ACTION_EXIT_CELL_ONLY_MODE),
Context.RECEIVER_NOT_EXPORTED);
resetCellOnlyMode();
}
public void addListener(Listener listener) {
mListeners.add(listener);
}
public boolean isCellOnlyModeEnabled() {
return mCellOnlyModeEnabled;
}
final class SettingsObserver extends ContentObserver {
public SettingsObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange, Uri uri) {
if (uri.equals(Settings.System.getUriFor(CELL_ONLY_MODE_SETTING_KEY))) {
mCellOnlyModeEnabled = fetchCellOnlyModeEnabled();
Log.d(TAG, "CellOnlyMode Settings changed: " + mCellOnlyModeEnabled);
for (Listener listener : mListeners) {
listener.onCellOnlyModeChanged(mCellOnlyModeEnabled);
}
if (mCellOnlyModeEnabled) {
mAlarmManager.setWindow(AlarmManager.ELAPSED_REALTIME,
SystemClock.elapsedRealtime() + getCellOnlyModeDuration(),
EXIT_CELL_ONLY_LINGER_DURATION_MS,
exitCellOnlyModeIntent);
} else {
mAlarmManager.cancel(exitCellOnlyModeIntent);
}
}
}
}
private void resetCellOnlyMode() {
mCellOnlyModeEnabled = false;
Settings.System.putInt(mContentResolver, CELL_ONLY_MODE_SETTING_KEY, 0);
}
private boolean fetchCellOnlyModeEnabled() {
return Settings.System.getInt(mContentResolver, CELL_ONLY_MODE_SETTING_KEY, 0) != 0;
}
@VisibleForTesting
long getCellOnlyModeDuration() {
int settingsDurationSeconds = Settings.System.getInt(mContentResolver, CELL_ONLY_MODE_DURATION_SECONDS_SETTING_KEY, -1);
if (settingsDurationSeconds <= 0) {
return DEFAULT_CELL_ONLY_MODE_DURATION_MS;
}
return Math.min(TimeUnit.SECONDS.toMillis(settingsDurationSeconds), MAX_CELL_ONLY_MODE_DURATION_MS);
}
}