1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.car.developeroptions.nfc; 17 18 import android.content.BroadcastReceiver; 19 import android.content.Context; 20 import android.content.Intent; 21 import android.content.IntentFilter; 22 import android.net.Uri; 23 import android.nfc.NfcAdapter; 24 import android.provider.Settings; 25 import android.util.Log; 26 import androidx.annotation.VisibleForTesting; 27 import androidx.preference.PreferenceScreen; 28 import androidx.preference.SwitchPreference; 29 30 import com.android.car.developeroptions.core.TogglePreferenceController; 31 import com.android.car.developeroptions.slices.SliceBackgroundWorker; 32 import com.android.settingslib.core.lifecycle.LifecycleObserver; 33 import com.android.settingslib.core.lifecycle.events.OnPause; 34 import com.android.settingslib.core.lifecycle.events.OnResume; 35 36 import java.io.IOException; 37 38 public class NfcPreferenceController extends TogglePreferenceController 39 implements LifecycleObserver, OnResume, OnPause { 40 41 public static final String KEY_TOGGLE_NFC = "toggle_nfc"; 42 private final NfcAdapter mNfcAdapter; 43 private NfcEnabler mNfcEnabler; 44 @VisibleForTesting 45 NfcAirplaneModeObserver mAirplaneModeObserver; 46 NfcPreferenceController(Context context, String key)47 public NfcPreferenceController(Context context, String key) { 48 super(context, key); 49 mNfcAdapter = NfcAdapter.getDefaultAdapter(context); 50 } 51 52 @Override displayPreference(PreferenceScreen screen)53 public void displayPreference(PreferenceScreen screen) { 54 super.displayPreference(screen); 55 if (!isAvailable()) { 56 mNfcEnabler = null; 57 return; 58 } 59 60 final SwitchPreference switchPreference = screen.findPreference(getPreferenceKey()); 61 62 mNfcEnabler = new NfcEnabler(mContext, switchPreference); 63 64 // Listen to airplane mode updates if NFC should be turned off when airplane mode is on 65 if (shouldTurnOffNFCInAirplaneMode(mContext) || isToggleableInAirplaneMode(mContext)) { 66 mAirplaneModeObserver = 67 new NfcAirplaneModeObserver(mContext, mNfcAdapter, switchPreference); 68 } 69 } 70 71 @Override isChecked()72 public boolean isChecked() { 73 return mNfcAdapter.isEnabled(); 74 } 75 76 @Override setChecked(boolean isChecked)77 public boolean setChecked(boolean isChecked) { 78 if (isChecked) { 79 mNfcAdapter.enable(); 80 } else { 81 mNfcAdapter.disable(); 82 } 83 return true; 84 } 85 86 @Override 87 @AvailabilityStatus getAvailabilityStatus()88 public int getAvailabilityStatus() { 89 return mNfcAdapter != null 90 ? AVAILABLE 91 : UNSUPPORTED_ON_DEVICE; 92 } 93 94 @Override hasAsyncUpdate()95 public boolean hasAsyncUpdate() { 96 return true; 97 } 98 99 @Override isSliceable()100 public boolean isSliceable() { 101 return true; 102 } 103 104 @Override getBackgroundWorkerClass()105 public Class<? extends SliceBackgroundWorker> getBackgroundWorkerClass() { 106 return NfcSliceWorker.class; 107 } 108 109 @Override onResume()110 public void onResume() { 111 if (mAirplaneModeObserver != null) { 112 mAirplaneModeObserver.register(); 113 } 114 if (mNfcEnabler != null) { 115 mNfcEnabler.resume(); 116 } 117 } 118 119 @Override onPause()120 public void onPause() { 121 if (mAirplaneModeObserver != null) { 122 mAirplaneModeObserver.unregister(); 123 } 124 if (mNfcEnabler != null) { 125 mNfcEnabler.pause(); 126 } 127 } 128 shouldTurnOffNFCInAirplaneMode(Context context)129 public static boolean shouldTurnOffNFCInAirplaneMode(Context context) { 130 final String airplaneModeRadios = Settings.Global.getString(context.getContentResolver(), 131 Settings.Global.AIRPLANE_MODE_RADIOS); 132 return airplaneModeRadios != null && airplaneModeRadios.contains(Settings.Global.RADIO_NFC); 133 } 134 isToggleableInAirplaneMode(Context context)135 public static boolean isToggleableInAirplaneMode(Context context) { 136 final String toggleable = Settings.Global.getString(context.getContentResolver(), 137 Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS); 138 return toggleable != null && toggleable.contains(Settings.Global.RADIO_NFC); 139 } 140 141 /** 142 * Listener for background changes to NFC. 143 * 144 * <p> 145 * Listen to broadcasts from {@link NfcAdapter}. The worker will call notify changed on the 146 * NFC Slice only when the following extras are present in the broadcast: 147 * <ul> 148 * <li>{@link NfcAdapter#STATE_ON}</li> 149 * <li>{@link NfcAdapter#STATE_OFF}</li> 150 * </ul> 151 */ 152 public static class NfcSliceWorker extends SliceBackgroundWorker<Void> { 153 154 private static final String TAG = "NfcSliceWorker"; 155 156 private static final IntentFilter NFC_FILTER = 157 new IntentFilter(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED); 158 159 private NfcUpdateReceiver mUpdateObserver; 160 NfcSliceWorker(Context context, Uri uri)161 public NfcSliceWorker(Context context, Uri uri) { 162 super(context, uri); 163 mUpdateObserver = new NfcUpdateReceiver(this); 164 } 165 166 @Override onSlicePinned()167 protected void onSlicePinned() { 168 getContext().registerReceiver(mUpdateObserver, NFC_FILTER); 169 } 170 171 @Override onSliceUnpinned()172 protected void onSliceUnpinned() { 173 getContext().unregisterReceiver(mUpdateObserver); 174 } 175 176 @Override close()177 public void close() throws IOException { 178 mUpdateObserver = null; 179 } 180 updateSlice()181 public void updateSlice() { 182 notifySliceChange(); 183 } 184 185 public class NfcUpdateReceiver extends BroadcastReceiver { 186 187 private final int NO_EXTRA = -1; 188 189 private final NfcSliceWorker mSliceBackgroundWorker; 190 NfcUpdateReceiver(NfcSliceWorker sliceWorker)191 public NfcUpdateReceiver(NfcSliceWorker sliceWorker) { 192 mSliceBackgroundWorker = sliceWorker; 193 } 194 195 @Override onReceive(Context context, Intent intent)196 public void onReceive(Context context, Intent intent) { 197 final int nfcStateExtra = intent.getIntExtra(NfcAdapter.EXTRA_ADAPTER_STATE, 198 NO_EXTRA); 199 200 // Do nothing if state change is empty, or an intermediate step. 201 if ( (nfcStateExtra == NO_EXTRA) 202 || (nfcStateExtra == NfcAdapter.STATE_TURNING_ON) 203 || (nfcStateExtra == NfcAdapter.STATE_TURNING_OFF)) { 204 Log.d(TAG, "Transitional update, dropping broadcast"); 205 return; 206 } 207 208 Log.d(TAG, "Nfc broadcast received, updating Slice."); 209 mSliceBackgroundWorker.updateSlice(); 210 } 211 } 212 } 213 } 214