diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 567400bdf9e..b68fca09fb4 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1716,6 +1716,16 @@ + + + + + + + + + + + + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 6ab0009f4b9..6965935e34b 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -700,6 +700,10 @@ Enable widgets Disabled by administrator + + NFC unlock + + @@ -4946,4 +4950,21 @@ + + + NFC Screen Lock Settings + Paired Tags + Enabled + Pair with a tag + + + NfcPairingActivity + Pair now + Set the tag flat on a table and place the phone on top + Tag and phone successfully paired, you are done! + The tag provided cannot be used to unlock your phone + The pairing with your tag failed, please try again + NFC is not enabled and is required for NFC Unlock. Please turn on NFC. + NFC Settings + diff --git a/res/xml/security_settings_biometric_weak.xml b/res/xml/security_settings_biometric_weak.xml index 7236673c178..c2e8ab7cb8d 100644 --- a/res/xml/security_settings_biometric_weak.xml +++ b/res/xml/security_settings_biometric_weak.xml @@ -61,6 +61,11 @@ android:title="@string/owner_info_settings_title" android:summary="@string/owner_info_settings_summary"/> + + diff --git a/res/xml/security_settings_chooser.xml b/res/xml/security_settings_chooser.xml index d29d221f65c..1a5431593cb 100644 --- a/res/xml/security_settings_chooser.xml +++ b/res/xml/security_settings_chooser.xml @@ -36,6 +36,11 @@ android:title="@string/owner_info_settings_title" android:summary="@string/owner_info_settings_summary"/> + + diff --git a/res/xml/security_settings_nfc_unlock.xml b/res/xml/security_settings_nfc_unlock.xml new file mode 100644 index 00000000000..c3442c604da --- /dev/null +++ b/res/xml/security_settings_nfc_unlock.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + diff --git a/res/xml/security_settings_password.xml b/res/xml/security_settings_password.xml index 7cde2f5d428..a102efa9cb6 100644 --- a/res/xml/security_settings_password.xml +++ b/res/xml/security_settings_password.xml @@ -48,6 +48,11 @@ android:title="@string/owner_info_settings_title" android:summary="@string/owner_info_settings_summary"/> + + diff --git a/res/xml/security_settings_pattern.xml b/res/xml/security_settings_pattern.xml index d8e06b3566a..0584e4ae247 100644 --- a/res/xml/security_settings_pattern.xml +++ b/res/xml/security_settings_pattern.xml @@ -52,6 +52,11 @@ android:title="@string/owner_info_settings_title" android:summary="@string/owner_info_settings_summary"/> + + diff --git a/res/xml/security_settings_pin.xml b/res/xml/security_settings_pin.xml index 1e8fbbeaba0..1417e217853 100644 --- a/res/xml/security_settings_pin.xml +++ b/res/xml/security_settings_pin.xml @@ -48,6 +48,11 @@ android:title="@string/owner_info_settings_title" android:summary="@string/owner_info_settings_summary"/> + + diff --git a/src/com/android/settings/NfcLockFragment.java b/src/com/android/settings/NfcLockFragment.java new file mode 100644 index 00000000000..b7bf6f768c6 --- /dev/null +++ b/src/com/android/settings/NfcLockFragment.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2013 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; + +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.nfc.NfcAdapter; +import android.nfc.NfcUnlock; +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.Preference; + +import android.preference.PreferenceCategory; +import android.util.Log; +import com.android.internal.widget.LockPatternUtils; + +import java.text.DateFormat; +import java.util.Date; + +import static android.preference.Preference.OnPreferenceClickListener; + +public class NfcLockFragment extends SettingsPreferenceFragment { + + private static final String NFC_PAIRING = "nfc_pairing"; + private static final String NFC_UNLOCK_ENABLED = "nfc_unlock_enabled"; + private static final String TAGS_CATEGORY = "nfc_unlock_tags_category"; + private static final String TAG_FORMAT = "Tag # %d"; + + private NfcUnlock mNfcUnlock; + private LockPatternUtils mLockPatternUtils; + private NfcAdapter mNfcAdapter; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mNfcUnlock = NfcUnlock.getInstance(NfcAdapter.getDefaultAdapter(getActivity())); + mLockPatternUtils = new LockPatternUtils(getActivity()); + mNfcAdapter = NfcAdapter.getDefaultAdapter(getActivity()); + addPreferencesFromResource(R.xml.security_settings_nfc_unlock); + } + + @Override + public void onResume() { + super.onResume(); + + boolean prefsEnabled = (mLockPatternUtils.isLockPasswordEnabled() || + mLockPatternUtils.isLockPatternEnabled()) && mNfcAdapter.isEnabled(); + CheckBoxPreference unlockPref = (CheckBoxPreference) findPreference(NFC_UNLOCK_ENABLED); + unlockPref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + mNfcUnlock.setNfcUnlockEnabled((Boolean) newValue); + return true; + } + }); + Preference pairingPref = findPreference(NFC_PAIRING); + unlockPref.setEnabled(prefsEnabled); + pairingPref.setEnabled(prefsEnabled); + + long[] tagRegistryTimes = mNfcUnlock.getTagRegistryTimes(); + unlockPref.setChecked(mNfcUnlock.getNfcUnlockEnabled()); + + final PreferenceCategory pairedTags = (PreferenceCategory) findPreference(TAGS_CATEGORY); + pairedTags.setEnabled(prefsEnabled); + + loadTagList(tagRegistryTimes, pairedTags); + } + + private void loadTagList(long[] tagRegistryTimes, final PreferenceCategory pairedTags) { + pairedTags.removeAll(); + + for (int i = 0; i < tagRegistryTimes.length; i++) { + + final Preference thisPreference = new Preference(getActivity()); + final long timestamp = tagRegistryTimes[i]; + + thisPreference.setTitle(String.format(TAG_FORMAT, i)); + thisPreference.setSummary( + DateFormat.getDateTimeInstance().format(new Date(tagRegistryTimes[i]))); + thisPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + + AlertDialog.Builder deleteDialogBuilder = new AlertDialog.Builder(getActivity()); + + deleteDialogBuilder.setTitle(thisPreference.getTitle()); + deleteDialogBuilder.setItems(new String[] {"Delete"}, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (which == 0) { + if (mNfcUnlock.deregisterTag(timestamp)) { + loadTagList(mNfcUnlock.getTagRegistryTimes(), + pairedTags); + } + } + } + }); + + + deleteDialogBuilder.show(); + + return true; + } + }); + + + pairedTags.addPreference(thisPreference); + } + } +} diff --git a/src/com/android/settings/PairNfcDevice.java b/src/com/android/settings/PairNfcDevice.java new file mode 100644 index 00000000000..638f0d78ea2 --- /dev/null +++ b/src/com/android/settings/PairNfcDevice.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2013 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; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.PendingIntent; +import android.content.DialogInterface; +import android.content.Intent; +import android.nfc.NfcAdapter; +import android.nfc.NfcUnlock; +import android.nfc.Tag; +import android.os.Bundle; +import android.os.Handler; +import android.os.PowerManager; +import android.provider.Settings; +import android.view.Menu; +import android.widget.ImageView; +import android.widget.TextView; + +public class PairNfcDevice extends Activity { + private static String TAG = PairNfcDevice.class.getName(); + + private TextView mStatusText; + private ImageView mStatusImage; + + private PendingIntent mPendingIntent; + private NfcAdapter mAdapter; + + private Handler mHandler = new Handler(); + private PowerManager.WakeLock mWakeLock; + + private NfcUnlock mNfcUnlock; + + // If pairing fails, we immediately get a new intent that would not leave time for the user to + // read the error message. So we'll just drop it and the user has to try again. + // TEST + private boolean mWaitingForDeviceDelayed; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_nfc_pairing); + + mStatusText = (TextView) findViewById(R.id.status_text); + mStatusImage = (ImageView) findViewById(R.id.status_image); + + mAdapter = NfcAdapter.getDefaultAdapter(this); + mPendingIntent = PendingIntent.getActivity( + this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0); + + mNfcUnlock = NfcUnlock.getInstance(mAdapter); + + setWaitingForDeviceMode(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + return false; + } + + public void onPause() { + super.onPause(); + mAdapter.disableForegroundDispatch(this); + if (mWakeLock != null) { + mWakeLock.release(); + } + } + + public void onResume() { + super.onResume(); + + if (!mAdapter.isEnabled()) { + // We need the user to start NFC. + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); + dialogBuilder.setMessage(R.string.enable_nfc); + dialogBuilder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + startActivity(new Intent(Settings.ACTION_NFC_SETTINGS)); + } + }); + dialogBuilder.show(); + } + + mAdapter.enableForegroundDispatch(this, mPendingIntent, null, null); + + if (mWakeLock == null) { + PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); + mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, TAG); + } + mWakeLock.acquire(); + } + + @Override + public void onNewIntent(Intent intent) { + Tag tag = (Tag) intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); + if (!mWaitingForDeviceDelayed) { + processTag(tag); + } + } + + private void processTag(Tag tag) { + if (mNfcUnlock.registerTag(tag)) { + setPairingSucceededMode(); + } else { + setPairingFailedMode(); + } + } + + private void setWaitingForDeviceModeDelayed(int delayInMs) { + mWaitingForDeviceDelayed = true; + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + mWaitingForDeviceDelayed = false; + setWaitingForDeviceMode(); + } + }, delayInMs); + } + + private void setWaitingForDeviceMode() { + mStatusImage.setImageResource(R.drawable.no_ring_detected); + mStatusText.setText(R.string.status_no_ring_detected); + } + + private void setPairingFailedMode() { + setErrorMode(R.string.status_error_pairing_failed); + } + + private void setPairingSucceededMode() { + mStatusImage.setImageResource(R.drawable.ring_paired); + mStatusText.setText(R.string.status_device_paired); + + // Automatically quit. + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + finish(); + } + }, 4000); + } + + private void setErrorMode(int errorMsgResourceId) { + mStatusText.setText(errorMsgResourceId); + setWaitingForDeviceModeDelayed(2500); + } +} diff --git a/src/com/android/settings/SecuritySettings.java b/src/com/android/settings/SecuritySettings.java index e4dcea1db7c..45c36ac295e 100644 --- a/src/com/android/settings/SecuritySettings.java +++ b/src/com/android/settings/SecuritySettings.java @@ -29,7 +29,9 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; +import android.nfc.NfcUnlock; import android.os.Bundle; +import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.preference.CheckBoxPreference; @@ -57,6 +59,7 @@ public class SecuritySettings extends RestrictedSettingsFragment // Lock Settings private static final String KEY_UNLOCK_SET_OR_CHANGE = "unlock_set_or_change"; + private static final String KEY_NFC_UNLOCK_SET_OR_CHANGE = "nfc_unlock_set_or_change"; private static final String KEY_BIOMETRIC_WEAK_IMPROVE_MATCHING = "biometric_weak_improve_matching"; private static final String KEY_BIOMETRIC_WEAK_LIVELINESS = "biometric_weak_liveliness"; @@ -225,6 +228,17 @@ public class SecuritySettings extends RestrictedSettingsFragment } } + // don't display NFC unlock settings if the prop is not enabled + if (!NfcUnlock.getPropertyEnabled()) { + PreferenceGroup securityCategory = + (PreferenceGroup) root.findPreference(KEY_SECURITY_CATEGORY); + + if (securityCategory != null) { + securityCategory.removePreference( + root.findPreference(KEY_NFC_UNLOCK_SET_OR_CHANGE)); + } + } + // Append the rest of the settings addPreferencesFromResource(R.xml.security_settings_misc); @@ -520,6 +534,9 @@ public class SecuritySettings extends RestrictedSettingsFragment if (KEY_UNLOCK_SET_OR_CHANGE.equals(key)) { startFragment(this, "com.android.settings.ChooseLockGeneric$ChooseLockGenericFragment", SET_OR_CHANGE_LOCK_METHOD_REQUEST, null); + } else if (KEY_NFC_UNLOCK_SET_OR_CHANGE.equals(key)) { + startFragment(this, "com.android.settings.NfcLockFragment", + SET_OR_CHANGE_LOCK_METHOD_REQUEST, null); } else if (KEY_BIOMETRIC_WEAK_IMPROVE_MATCHING.equals(key)) { ChooseLockSettingsHelper helper = new ChooseLockSettingsHelper(this.getActivity(), this);