private static List<VpnProfile> loadVpnProfiles(KeyStore keyStore, int... excludeTypes) {
   final ArrayList<VpnProfile> result = Lists.newArrayList();
   final String[] keys = keyStore.saw(Credentials.VPN);
   if (keys != null) {
     for (String key : keys) {
       final VpnProfile profile = VpnProfile.decode(key, keyStore.get(Credentials.VPN + key));
       if (profile != null && !ArrayUtils.contains(excludeTypes, profile.type)) {
         result.add(profile);
       }
     }
   }
   return result;
 }
    @Override
    public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);

      mDPM = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
      mKeyStore = KeyStore.getInstance();
      mChooseLockSettingsHelper = new ChooseLockSettingsHelper(this.getActivity());

      // Defaults to needing to confirm credentials
      final boolean confirmCredentials =
          getActivity().getIntent().getBooleanExtra(CONFIRM_CREDENTIALS, true);
      if (getActivity() instanceof ChooseLockGeneric.InternalActivity) {
        mPasswordConfirmed = !confirmCredentials;
      }

      if (savedInstanceState != null) {
        mPasswordConfirmed = savedInstanceState.getBoolean(PASSWORD_CONFIRMED);
        mWaitingForConfirmation = savedInstanceState.getBoolean(WAITING_FOR_CONFIRMATION);
        mFinishPending = savedInstanceState.getBoolean(FINISH_PENDING);
      }

      if (mPasswordConfirmed) {
        updatePreferencesOrFinish();
      } else if (!mWaitingForConfirmation) {
        ChooseLockSettingsHelper helper = new ChooseLockSettingsHelper(this.getActivity(), this);
        if (!helper.launchConfirmationActivity(CONFIRM_EXISTING_REQUEST, null, null)) {
          mPasswordConfirmed = true; // no password set, so no need to confirm
          updatePreferencesOrFinish();
        } else {
          mWaitingForConfirmation = true;
        }
      }
    }
  @Override
  public boolean onContextItemSelected(MenuItem item) {
    if (mDialog != null) {
      Log.v(TAG, "onContextItemSelected() is called when mDialog != null");
      return false;
    }

    VpnPreference preference = mPreferences.get(mSelectedKey);
    if (preference == null) {
      Log.v(TAG, "onContextItemSelected() is called but no preference is found");
      return false;
    }

    switch (item.getItemId()) {
      case R.string.vpn_menu_edit:
        mDialog = new VpnDialog(getActivity(), this, preference.getProfile(), true);
        mDialog.setOnDismissListener(this);
        mDialog.show();
        return true;
      case R.string.vpn_menu_delete:
        disconnect(mSelectedKey);
        getPreferenceScreen().removePreference(preference);
        mPreferences.remove(mSelectedKey);
        mKeyStore.delete(Credentials.VPN + mSelectedKey);
        return true;
    }
    return false;
  }
  @Override
  public void onClick(DialogInterface dialog, int button) {
    if (button == DialogInterface.BUTTON_POSITIVE) {
      // Always save the profile.
      VpnProfile profile = mDialog.getProfile();
      mKeyStore.put(Credentials.VPN + profile.key, profile.encode());

      // Update the preference.
      VpnPreference preference = mPreferences.get(profile.key);
      if (preference != null) {
        disconnect(profile.key);
        preference.update(profile);
      } else {
        preference = new VpnPreference(getActivity(), profile);
        preference.setOnPreferenceClickListener(this);
        mPreferences.put(profile.key, preference);
        getPreferenceScreen().addPreference(preference);
      }

      // If we are not editing, connect!
      if (!mDialog.isEditing()) {
        try {
          connect(profile);
        } catch (Exception e) {
          Log.e(TAG, "connect", e);
        }
      }
    }
  }
 private int upgradeQualityForKeyStore(int quality) {
   if (!mKeyStore.isEmpty()) {
     if (quality < CredentialStorage.MIN_PASSWORD_QUALITY) {
       quality = CredentialStorage.MIN_PASSWORD_QUALITY;
     }
   }
   return quality;
 }
 /**
  * Save a lock pattern.
  *
  * @param pattern The new pattern to save.
  * @param isFallback Specifies if this is a fallback to biometric weak
  */
 public void saveLockPattern(List<LockPatternView.Cell> pattern, boolean isFallback) {
   // Compute the hash
   final byte[] hash = LockPatternUtils.patternToHash(pattern);
   try {
     // Write the hash to file
     RandomAccessFile raf = new RandomAccessFile(sLockPatternFilename, "rw");
     // Truncate the file if pattern is null, to clear the lock
     if (pattern == null) {
       raf.setLength(0);
     } else {
       raf.write(hash, 0, hash.length);
     }
     raf.close();
     DevicePolicyManager dpm = getDevicePolicyManager();
     KeyStore keyStore = KeyStore.getInstance();
     if (pattern != null) {
       keyStore.password(patternToString(pattern));
       setBoolean(PATTERN_EVER_CHOSEN_KEY, true);
       if (!isFallback) {
         deleteGallery();
         setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
         dpm.setActivePasswordState(
             DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, pattern.size(), 0, 0, 0, 0, 0, 0);
       } else {
         setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK);
         setLong(PASSWORD_TYPE_ALTERNATE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
         finishBiometricWeak();
         dpm.setActivePasswordState(
             DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK, 0, 0, 0, 0, 0, 0, 0);
       }
     } else {
       if (keyStore.isEmpty()) {
         keyStore.reset();
       }
       dpm.setActivePasswordState(
           DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0, 0);
     }
   } catch (FileNotFoundException fnfe) {
     // Cant do much, unless we want to fail over to using the settings
     // provider
     Log.e(TAG, "Unable to save lock pattern to " + sLockPatternFilename);
   } catch (IOException ioe) {
     // Cant do much
     Log.e(TAG, "Unable to save lock pattern to " + sLockPatternFilename);
   }
 }
 private void resetWhilePreservingInitState() {
   IBinder operationToken = mOperationToken;
   if (operationToken != null) {
     mKeyStore.abort(operationToken);
   }
   mOperationToken = null;
   mOperationHandle = 0;
   mChunkedStreamer = null;
 }
 private void resetAll() {
   mKey = null;
   IBinder operationToken = mOperationToken;
   if (operationToken != null) {
     mKeyStore.abort(operationToken);
   }
   mOperationToken = null;
   mOperationHandle = 0;
   mChunkedStreamer = null;
 }
 @Override
 public void finalize() throws Throwable {
   try {
     IBinder operationToken = mOperationToken;
     if (operationToken != null) {
       mKeyStore.abort(operationToken);
     }
   } finally {
     super.finalize();
   }
 }
  @Override
  public void onResume() {
    super.onResume();

    // Check KeyStore here, so others do not need to deal with it.
    if (!mKeyStore.isUnlocked()) {
      if (!mUnlocking) {
        // Let us unlock KeyStore. See you later!
        Credentials.getInstance().unlock(getActivity());
      } else {
        // We already tried, but it is still not working!
        finishFragment();
      }
      mUnlocking = !mUnlocking;
      return;
    }

    // Now KeyStore is always unlocked. Reset the flag.
    mUnlocking = false;

    // Currently we are the only user of profiles in KeyStore.
    // Assuming KeyStore and KeyGuard do the right thing, we can
    // safely cache profiles in the memory.
    if (mPreferences.size() == 0) {
      PreferenceGroup group = getPreferenceScreen();

      final Context context = getActivity();
      final List<VpnProfile> profiles = loadVpnProfiles(mKeyStore);
      for (VpnProfile profile : profiles) {
        final VpnPreference pref = new VpnPreference(context, profile);
        pref.setOnPreferenceClickListener(this);
        mPreferences.put(profile.key, pref);
        group.addPreference(pref);
      }
    }

    // Show the dialog if there is one.
    if (mDialog != null) {
      mDialog.setOnDismissListener(this);
      mDialog.show();
    }

    // Start monitoring.
    if (mUpdater == null) {
      mUpdater = new Handler(this);
    }
    mUpdater.sendEmptyMessage(0);

    // Register for context menu. Hmmm, getListView() is hidden?
    registerForContextMenu(getListView());
  }
  private void ensureKeystoreOperationInitialized() throws InvalidKeyException {
    if (mChunkedStreamer != null) {
      return;
    }
    if (mKey == null) {
      throw new IllegalStateException("Not initialized");
    }

    KeymasterArguments keymasterArgs = new KeymasterArguments();
    keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_HMAC);
    keymasterArgs.addEnum(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest);
    keymasterArgs.addUnsignedInt(KeymasterDefs.KM_TAG_MAC_LENGTH, mMacSizeBits);

    OperationResult opResult =
        mKeyStore.begin(
            mKey.getAlias(),
            KeymasterDefs.KM_PURPOSE_SIGN,
            true,
            keymasterArgs,
            null, // no additional entropy needed for HMAC because it's deterministic
            mKey.getUid());

    if (opResult == null) {
      throw new KeyStoreConnectException();
    }

    // Store operation token and handle regardless of the error code returned by KeyStore to
    // ensure that the operation gets aborted immediately if the code below throws an exception.
    mOperationToken = opResult.token;
    mOperationHandle = opResult.operationHandle;

    // If necessary, throw an exception due to KeyStore operation having failed.
    InvalidKeyException e =
        KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit(
            mKeyStore, mKey, opResult.resultCode);
    if (e != null) {
      throw e;
    }

    if (mOperationToken == null) {
      throw new ProviderException("Keystore returned null operation token");
    }
    if (mOperationHandle == 0) {
      throw new ProviderException("Keystore returned invalid operation handle");
    }

    mChunkedStreamer =
        new KeyStoreCryptoOperationChunkedStreamer(
            new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(mKeyStore, mOperationToken));
  }
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
      final Context context = getActivity();
      final KeyStore keyStore = KeyStore.getInstance();

      initProfiles(keyStore, context.getResources());

      final AlertDialog.Builder builder = new AlertDialog.Builder(context);
      final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());

      builder.setTitle(R.string.vpn_menu_lockdown);

      final View view = dialogInflater.inflate(R.layout.vpn_lockdown_editor, null, false);
      final ListView listView = (ListView) view.findViewById(android.R.id.list);
      listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
      listView.setAdapter(new TitleAdapter(context, mTitles));
      listView.setItemChecked(mCurrentIndex, true);
      builder.setView(view);

      builder.setPositiveButton(
          android.R.string.ok,
          new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
              final int newIndex = listView.getCheckedItemPosition();
              if (mCurrentIndex == newIndex) return;

              if (newIndex == 0) {
                keyStore.delete(Credentials.LOCKDOWN_VPN);

              } else {
                final VpnProfile profile = mProfiles.get(newIndex - 1);
                if (!profile.isValidLockdownProfile()) {
                  Toast.makeText(context, R.string.vpn_lockdown_config_error, Toast.LENGTH_LONG)
                      .show();
                  return;
                }
                keyStore.put(Credentials.LOCKDOWN_VPN, profile.key.getBytes());
              }

              // kick profiles since we changed them
              ConnectivityManager.from(getActivity()).updateLockdownVpn();
            }
          });

      return builder.create();
    }
    @Override
    public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);

      mDPM = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
      mKeyStore = KeyStore.getInstance();
      mChooseLockSettingsHelper = new ChooseLockSettingsHelper(this.getActivity());

      // Defaults to needing to confirm credentials
      final boolean confirmCredentials =
          getActivity().getIntent().getBooleanExtra(CONFIRM_CREDENTIALS, true);
      mPasswordConfirmed = !confirmCredentials;

      if (savedInstanceState != null) {
        mPasswordConfirmed = savedInstanceState.getBoolean(PASSWORD_CONFIRMED);
      }
    }
  private void loadCertificates(Spinner spinner, String prefix) {
    final Context context = mConfigUi.getContext();

    String[] certs = KeyStore.getInstance().saw(prefix, android.os.Process.WIFI_UID);
    if (certs == null || certs.length == 0) {
      certs = new String[] {unspecifiedCert};
    } else {
      final String[] array = new String[certs.length + 1];
      array[0] = unspecifiedCert;
      System.arraycopy(certs, 0, array, 1, certs.length);
      certs = array;
    }

    final ArrayAdapter<String> adapter =
        new ArrayAdapter<String>(context, android.R.layout.simple_spinner_item, certs);
    adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    spinner.setAdapter(adapter);
  }
    public void onPatternDetected(List<LockPatternView.Cell> pattern) {
      if (mLockPatternUtils.checkPattern(pattern)) {
        mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct);
        mKeyguardStatusViewManager.setInstructionText("");
        mKeyguardStatusViewManager.updateStatusLines(true);
        mCallback.keyguardDone(true);
        mCallback.reportSuccessfulUnlockAttempt();
        KeyStore.getInstance().password(LockPatternUtils.patternToString(pattern));
      } else {
        boolean reportFailedAttempt = false;
        if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
          mCallback.pokeWakelock(UNLOCK_PATTERN_WAKE_INTERVAL_MS);
        }
        mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
        if (pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
          mTotalFailedPatternAttempts++;
          mFailedPatternAttemptsSinceLastTimeout++;
          reportFailedAttempt = true;
        }
        if (mFailedPatternAttemptsSinceLastTimeout
            >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) {
          long deadline = mLockPatternUtils.setLockoutAttemptDeadline();
          handleAttemptLockout(deadline);
        } else {
          // TODO mUnlockIcon.setVisibility(View.VISIBLE);
          mKeyguardStatusViewManager.setInstructionText(
              getContext().getString(R.string.lockscreen_pattern_wrong));
          mKeyguardStatusViewManager.updateStatusLines(true);
          mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS);
        }

        // Because the following can result in cleanUp() being called on this screen,
        // member variables reset in cleanUp() shouldn't be accessed after this call.
        if (reportFailedAttempt) {
          mCallback.reportFailedUnlockAttempt();
        }
      }
    }
  @Override
  public void onResume() {
    super.onResume();

    // Make sure we reload the preference hierarchy since some of these settings
    // depend on others...
    createPreferenceHierarchy();

    final LockPatternUtils lockPatternUtils = mChooseLockSettingsHelper.utils();
    if (mVisiblePattern != null) {
      mVisiblePattern.setChecked(lockPatternUtils.isVisiblePatternEnabled());
    }
    if (mTactileFeedback != null) {
      mTactileFeedback.setChecked(lockPatternUtils.isTactileFeedbackEnabled());
    }
    if (mPowerButtonInstantlyLocks != null) {
      mPowerButtonInstantlyLocks.setChecked(lockPatternUtils.getPowerButtonInstantlyLocks());
    }

    mShowPassword.setChecked(
        Settings.System.getInt(getContentResolver(), Settings.System.TEXT_SHOW_PASSWORD, 1) != 0);

    KeyStore.State state = KeyStore.getInstance().state();
    mResetCredentials.setEnabled(state != KeyStore.State.UNINITIALIZED);

    // Location Settings
    updateLocationToggles();

    if (mSettingsObserver == null) {
      mSettingsObserver =
          new Observer() {
            public void update(Observable o, Object arg) {
              updateLocationToggles();
            }
          };
      mContentQueryMap.addObserver(mSettingsObserver);
    }
  }
 private void verifyPasswordAndUnlock() {
   String entry = mPasswordEntry.getText().toString();
   if (mLockPatternUtils.checkPassword(entry)) {
     mCallback.keyguardDone(true);
     mCallback.reportSuccessfulUnlockAttempt();
     mStatusViewManager.setInstructionText(null);
     KeyStore.getInstance().password(entry);
   } else if (entry.length() > MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT) {
     // to avoid accidental lockout, only count attempts that are long enough to be a
     // real password. This may require some tweaking.
     mCallback.reportFailedUnlockAttempt();
     if (0
         == (mUpdateMonitor.getFailedAttempts()
             % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) {
       long deadline = mLockPatternUtils.setLockoutAttemptDeadline();
       handleAttemptLockout(deadline);
     }
     mStatusViewManager.setInstructionText(mContext.getString(R.string.lockscreen_password_wrong));
   } else if (entry.length() > 0) {
     mStatusViewManager.setInstructionText(mContext.getString(R.string.lockscreen_password_wrong));
   }
   mPasswordEntry.setText("");
 }
public class VpnSettings extends SettingsPreferenceFragment
    implements Handler.Callback,
        Preference.OnPreferenceClickListener,
        DialogInterface.OnClickListener,
        DialogInterface.OnDismissListener {
  private static final String TAG = "VpnSettings";

  private static final String TAG_LOCKDOWN = "lockdown";

  // TODO: migrate to using DialogFragment when editing

  private final IConnectivityManager mService =
      IConnectivityManager.Stub.asInterface(
          ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
  private final KeyStore mKeyStore = KeyStore.getInstance();
  private boolean mUnlocking = false;

  private HashMap<String, VpnPreference> mPreferences = new HashMap<String, VpnPreference>();
  private VpnDialog mDialog;

  private Handler mUpdater;
  private LegacyVpnInfo mInfo;

  // The key of the profile for the current ContextMenu.
  private String mSelectedKey;

  @Override
  public void onCreate(Bundle savedState) {
    super.onCreate(savedState);

    setHasOptionsMenu(true);
    addPreferencesFromResource(R.xml.vpn_settings2);

    if (savedState != null) {
      VpnProfile profile =
          VpnProfile.decode(savedState.getString("VpnKey"), savedState.getByteArray("VpnProfile"));
      if (profile != null) {
        mDialog = new VpnDialog(getActivity(), this, profile, savedState.getBoolean("VpnEditing"));
      }
    }
  }

  @Override
  public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    super.onCreateOptionsMenu(menu, inflater);
    inflater.inflate(R.menu.vpn, menu);
  }

  @Override
  public void onPrepareOptionsMenu(Menu menu) {
    super.onPrepareOptionsMenu(menu);

    // Hide lockdown VPN on devices that require IMS authentication
    if (SystemProperties.getBoolean("persist.radio.imsregrequired", false)) {
      menu.findItem(R.id.vpn_lockdown).setVisible(false);
    }
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
      case R.id.vpn_create:
        {
          // Generate a new key. Here we just use the current time.
          long millis = System.currentTimeMillis();
          while (mPreferences.containsKey(Long.toHexString(millis))) {
            ++millis;
          }
          mDialog =
              new VpnDialog(getActivity(), this, new VpnProfile(Long.toHexString(millis)), true);
          mDialog.setOnDismissListener(this);
          mDialog.show();
          return true;
        }
      case R.id.vpn_lockdown:
        {
          LockdownConfigFragment.show(this);
          return true;
        }
    }
    return super.onOptionsItemSelected(item);
  }

  @Override
  public void onSaveInstanceState(Bundle savedState) {
    // We do not save view hierarchy, as they are just profiles.
    if (mDialog != null) {
      VpnProfile profile = mDialog.getProfile();
      savedState.putString("VpnKey", profile.key);
      savedState.putByteArray("VpnProfile", profile.encode());
      savedState.putBoolean("VpnEditing", mDialog.isEditing());
    }
    // else?
  }

  @Override
  public void onResume() {
    super.onResume();

    // Check KeyStore here, so others do not need to deal with it.
    if (!mKeyStore.isUnlocked()) {
      if (!mUnlocking) {
        // Let us unlock KeyStore. See you later!
        Credentials.getInstance().unlock(getActivity());
      } else {
        // We already tried, but it is still not working!
        finishFragment();
      }
      mUnlocking = !mUnlocking;
      return;
    }

    // Now KeyStore is always unlocked. Reset the flag.
    mUnlocking = false;

    // Currently we are the only user of profiles in KeyStore.
    // Assuming KeyStore and KeyGuard do the right thing, we can
    // safely cache profiles in the memory.
    if (mPreferences.size() == 0) {
      PreferenceGroup group = getPreferenceScreen();

      final Context context = getActivity();
      final List<VpnProfile> profiles = loadVpnProfiles(mKeyStore);
      for (VpnProfile profile : profiles) {
        final VpnPreference pref = new VpnPreference(context, profile);
        pref.setOnPreferenceClickListener(this);
        mPreferences.put(profile.key, pref);
        group.addPreference(pref);
      }
    }

    // Show the dialog if there is one.
    if (mDialog != null) {
      mDialog.setOnDismissListener(this);
      mDialog.show();
    }

    // Start monitoring.
    if (mUpdater == null) {
      mUpdater = new Handler(this);
    }
    mUpdater.sendEmptyMessage(0);

    // Register for context menu. Hmmm, getListView() is hidden?
    registerForContextMenu(getListView());
  }

  @Override
  public void onPause() {
    super.onPause();

    // Hide the dialog if there is one.
    if (mDialog != null) {
      mDialog.setOnDismissListener(null);
      mDialog.dismiss();
    }

    // Unregister for context menu.
    if (getView() != null) {
      unregisterForContextMenu(getListView());
    }
  }

  @Override
  public void onDismiss(DialogInterface dialog) {
    // Here is the exit of a dialog.
    mDialog = null;
  }

  @Override
  public void onClick(DialogInterface dialog, int button) {
    if (button == DialogInterface.BUTTON_POSITIVE) {
      // Always save the profile.
      VpnProfile profile = mDialog.getProfile();
      mKeyStore.put(Credentials.VPN + profile.key, profile.encode());

      // Update the preference.
      VpnPreference preference = mPreferences.get(profile.key);
      if (preference != null) {
        disconnect(profile.key);
        preference.update(profile);
      } else {
        preference = new VpnPreference(getActivity(), profile);
        preference.setOnPreferenceClickListener(this);
        mPreferences.put(profile.key, preference);
        getPreferenceScreen().addPreference(preference);
      }

      // If we are not editing, connect!
      if (!mDialog.isEditing()) {
        try {
          connect(profile);
        } catch (Exception e) {
          Log.e(TAG, "connect", e);
        }
      }
    }
  }

  @Override
  public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) {
    if (mDialog != null) {
      Log.v(TAG, "onCreateContextMenu() is called when mDialog != null");
      return;
    }

    if (info instanceof AdapterContextMenuInfo) {
      Preference preference =
          (Preference) getListView().getItemAtPosition(((AdapterContextMenuInfo) info).position);
      if (preference instanceof VpnPreference) {
        VpnProfile profile = ((VpnPreference) preference).getProfile();
        mSelectedKey = profile.key;
        menu.setHeaderTitle(profile.name);
        menu.add(Menu.NONE, R.string.vpn_menu_edit, 0, R.string.vpn_menu_edit);
        menu.add(Menu.NONE, R.string.vpn_menu_delete, 0, R.string.vpn_menu_delete);
      }
    }
  }

  @Override
  public boolean onContextItemSelected(MenuItem item) {
    if (mDialog != null) {
      Log.v(TAG, "onContextItemSelected() is called when mDialog != null");
      return false;
    }

    VpnPreference preference = mPreferences.get(mSelectedKey);
    if (preference == null) {
      Log.v(TAG, "onContextItemSelected() is called but no preference is found");
      return false;
    }

    switch (item.getItemId()) {
      case R.string.vpn_menu_edit:
        mDialog = new VpnDialog(getActivity(), this, preference.getProfile(), true);
        mDialog.setOnDismissListener(this);
        mDialog.show();
        return true;
      case R.string.vpn_menu_delete:
        disconnect(mSelectedKey);
        getPreferenceScreen().removePreference(preference);
        mPreferences.remove(mSelectedKey);
        mKeyStore.delete(Credentials.VPN + mSelectedKey);
        return true;
    }
    return false;
  }

  @Override
  public boolean onPreferenceClick(Preference preference) {
    if (mDialog != null) {
      Log.v(TAG, "onPreferenceClick() is called when mDialog != null");
      return true;
    }

    if (preference instanceof VpnPreference) {
      VpnProfile profile = ((VpnPreference) preference).getProfile();
      if (mInfo != null
          && profile.key.equals(mInfo.key)
          && mInfo.state == LegacyVpnInfo.STATE_CONNECTED) {
        try {
          mInfo.intent.send();
          return true;
        } catch (Exception e) {
          // ignore
        }
      }
      mDialog = new VpnDialog(getActivity(), this, profile, false);
    } else {
      // Generate a new key. Here we just use the current time.
      long millis = System.currentTimeMillis();
      while (mPreferences.containsKey(Long.toHexString(millis))) {
        ++millis;
      }
      mDialog = new VpnDialog(getActivity(), this, new VpnProfile(Long.toHexString(millis)), true);
    }
    mDialog.setOnDismissListener(this);
    mDialog.show();
    return true;
  }

  @Override
  public boolean handleMessage(Message message) {
    mUpdater.removeMessages(0);

    if (isResumed()) {
      try {
        LegacyVpnInfo info = mService.getLegacyVpnInfo();
        if (mInfo != null) {
          VpnPreference preference = mPreferences.get(mInfo.key);
          if (preference != null) {
            preference.update(-1);
          }
          mInfo = null;
        }
        if (info != null) {
          VpnPreference preference = mPreferences.get(info.key);
          if (preference != null) {
            preference.update(info.state);
            mInfo = info;
          }
        }
      } catch (Exception e) {
        // ignore
      }
      mUpdater.sendEmptyMessageDelayed(0, 1000);
    }
    return true;
  }

  private void connect(VpnProfile profile) throws Exception {
    try {
      mService.startLegacyVpn(profile);
    } catch (IllegalStateException e) {
      Toast.makeText(getActivity(), R.string.vpn_no_network, Toast.LENGTH_LONG).show();
    }
  }

  private void disconnect(String key) {
    if (mInfo != null && key.equals(mInfo.key)) {
      try {
        mService.prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN);
      } catch (Exception e) {
        // ignore
      }
    }
  }

  @Override
  protected int getHelpResource() {
    return R.string.help_url_vpn;
  }

  private static class VpnPreference extends Preference {
    private VpnProfile mProfile;
    private int mState = -1;

    VpnPreference(Context context, VpnProfile profile) {
      super(context);
      setPersistent(false);
      setOrder(0);

      mProfile = profile;
      update();
    }

    VpnProfile getProfile() {
      return mProfile;
    }

    void update(VpnProfile profile) {
      mProfile = profile;
      update();
    }

    void update(int state) {
      mState = state;
      update();
    }

    void update() {
      if (mState < 0) {
        String[] types = getContext().getResources().getStringArray(R.array.vpn_types_long);
        setSummary(types[mProfile.type]);
      } else {
        String[] states = getContext().getResources().getStringArray(R.array.vpn_states);
        setSummary(states[mState]);
      }
      setTitle(mProfile.name);
      notifyHierarchyChanged();
    }

    @Override
    public int compareTo(Preference preference) {
      int result = -1;
      if (preference instanceof VpnPreference) {
        VpnPreference another = (VpnPreference) preference;
        if ((result = another.mState - mState) == 0
            && (result = mProfile.name.compareTo(another.mProfile.name)) == 0
            && (result = mProfile.type - another.mProfile.type) == 0) {
          result = mProfile.key.compareTo(another.mProfile.key);
        }
      }
      return result;
    }
  }

  /** Dialog to configure always-on VPN. */
  public static class LockdownConfigFragment extends DialogFragment {
    private List<VpnProfile> mProfiles;
    private List<CharSequence> mTitles;
    private int mCurrentIndex;

    private static class TitleAdapter extends ArrayAdapter<CharSequence> {
      public TitleAdapter(Context context, List<CharSequence> objects) {
        super(
            context,
            com.android.internal.R.layout.select_dialog_singlechoice_holo,
            android.R.id.text1,
            objects);
      }
    }

    public static void show(VpnSettings parent) {
      if (!parent.isAdded()) return;

      final LockdownConfigFragment dialog = new LockdownConfigFragment();
      dialog.show(parent.getFragmentManager(), TAG_LOCKDOWN);
    }

    private static String getStringOrNull(KeyStore keyStore, String key) {
      final byte[] value = keyStore.get(Credentials.LOCKDOWN_VPN);
      return value == null ? null : new String(value);
    }

    private void initProfiles(KeyStore keyStore, Resources res) {
      final String lockdownKey = getStringOrNull(keyStore, Credentials.LOCKDOWN_VPN);

      mProfiles = loadVpnProfiles(keyStore, VpnProfile.TYPE_PPTP);
      mTitles = Lists.newArrayList();
      mTitles.add(res.getText(R.string.vpn_lockdown_none));
      mCurrentIndex = 0;

      for (VpnProfile profile : mProfiles) {
        if (TextUtils.equals(profile.key, lockdownKey)) {
          mCurrentIndex = mTitles.size();
        }
        mTitles.add(profile.name);
      }
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
      final Context context = getActivity();
      final KeyStore keyStore = KeyStore.getInstance();

      initProfiles(keyStore, context.getResources());

      final AlertDialog.Builder builder = new AlertDialog.Builder(context);
      final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());

      builder.setTitle(R.string.vpn_menu_lockdown);

      final View view = dialogInflater.inflate(R.layout.vpn_lockdown_editor, null, false);
      final ListView listView = (ListView) view.findViewById(android.R.id.list);
      listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
      listView.setAdapter(new TitleAdapter(context, mTitles));
      listView.setItemChecked(mCurrentIndex, true);
      builder.setView(view);

      builder.setPositiveButton(
          android.R.string.ok,
          new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
              final int newIndex = listView.getCheckedItemPosition();
              if (mCurrentIndex == newIndex) return;

              if (newIndex == 0) {
                keyStore.delete(Credentials.LOCKDOWN_VPN);

              } else {
                final VpnProfile profile = mProfiles.get(newIndex - 1);
                if (!profile.isValidLockdownProfile()) {
                  Toast.makeText(context, R.string.vpn_lockdown_config_error, Toast.LENGTH_LONG)
                      .show();
                  return;
                }
                keyStore.put(Credentials.LOCKDOWN_VPN, profile.key.getBytes());
              }

              // kick profiles since we changed them
              ConnectivityManager.from(getActivity()).updateLockdownVpn();
            }
          });

      return builder.create();
    }
  }

  private static List<VpnProfile> loadVpnProfiles(KeyStore keyStore, int... excludeTypes) {
    final ArrayList<VpnProfile> result = Lists.newArrayList();
    final String[] keys = keyStore.saw(Credentials.VPN);
    if (keys != null) {
      for (String key : keys) {
        final VpnProfile profile = VpnProfile.decode(key, keyStore.get(Credentials.VPN + key));
        if (profile != null && !ArrayUtils.contains(excludeTypes, profile.type)) {
          result.add(profile);
        }
      }
    }
    return result;
  }
}
  /**
   * Save a lock password. Does not ensure that the password is as good as the requested mode, but
   * will adjust the mode to be as good as the pattern.
   *
   * @param password The password to save
   * @param quality {@see DevicePolicyManager#getPasswordQuality(android.content.ComponentName)}
   * @param isFallback Specifies if this is a fallback to biometric weak
   */
  public void saveLockPassword(String password, int quality, boolean isFallback) {
    // Compute the hash
    final byte[] hash = passwordToHash(password);
    try {
      // Write the hash to file
      RandomAccessFile raf = new RandomAccessFile(sLockPasswordFilename, "rw");
      // Truncate the file if pattern is null, to clear the lock
      if (password == null) {
        raf.setLength(0);
      } else {
        raf.write(hash, 0, hash.length);
      }
      raf.close();
      DevicePolicyManager dpm = getDevicePolicyManager();
      KeyStore keyStore = KeyStore.getInstance();
      if (password != null) {
        // Update the encryption password.
        updateEncryptionPassword(password);

        // Update the keystore password
        keyStore.password(password);

        int computedQuality = computePasswordQuality(password);
        if (!isFallback) {
          deleteGallery();
          setLong(PASSWORD_TYPE_KEY, Math.max(quality, computedQuality));
          if (computedQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
            int letters = 0;
            int uppercase = 0;
            int lowercase = 0;
            int numbers = 0;
            int symbols = 0;
            int nonletter = 0;
            for (int i = 0; i < password.length(); i++) {
              char c = password.charAt(i);
              if (c >= 'A' && c <= 'Z') {
                letters++;
                uppercase++;
              } else if (c >= 'a' && c <= 'z') {
                letters++;
                lowercase++;
              } else if (c >= '0' && c <= '9') {
                numbers++;
                nonletter++;
              } else {
                symbols++;
                nonletter++;
              }
            }
            dpm.setActivePasswordState(
                Math.max(quality, computedQuality),
                password.length(),
                letters,
                uppercase,
                lowercase,
                numbers,
                symbols,
                nonletter);
          } else {
            // The password is not anything.
            dpm.setActivePasswordState(
                DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0, 0);
          }
        } else {
          // Case where it's a fallback for biometric weak
          setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK);
          setLong(PASSWORD_TYPE_ALTERNATE_KEY, Math.max(quality, computedQuality));
          finishBiometricWeak();
          dpm.setActivePasswordState(
              DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK, 0, 0, 0, 0, 0, 0, 0);
        }
        // Add the password to the password history. We assume all
        // password
        // hashes have the same length for simplicity of implementation.
        String passwordHistory = getString(PASSWORD_HISTORY_KEY);
        if (passwordHistory == null) {
          passwordHistory = new String();
        }
        int passwordHistoryLength = getRequestedPasswordHistoryLength();
        if (passwordHistoryLength == 0) {
          passwordHistory = "";
        } else {
          passwordHistory = new String(hash) + "," + passwordHistory;
          // Cut it to contain passwordHistoryLength hashes
          // and passwordHistoryLength -1 commas.
          passwordHistory =
              passwordHistory.substring(
                  0,
                  Math.min(
                      hash.length * passwordHistoryLength + passwordHistoryLength - 1,
                      passwordHistory.length()));
        }
        setString(PASSWORD_HISTORY_KEY, passwordHistory);
      } else {
        // Conditionally reset the keystore if empty. If
        // non-empty, we are just switching key guard type
        if (keyStore.isEmpty()) {
          keyStore.reset();
        }
        dpm.setActivePasswordState(
            DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0, 0);
      }
    } catch (FileNotFoundException fnfe) {
      // Cant do much, unless we want to fail over to using the settings provider
      Log.e(TAG, "Unable to save lock pattern to " + sLockPasswordFilename);
    } catch (IOException ioe) {
      // Cant do much
      Log.e(TAG, "Unable to save lock pattern to " + sLockPasswordFilename);
    }
  }
 private static String getStringOrNull(KeyStore keyStore, String key) {
   final byte[] value = keyStore.get(Credentials.LOCKDOWN_VPN);
   return value == null ? null : new String(value);
 }
/**
 * {@link MacSpi} which provides HMAC implementations backed by Android KeyStore.
 *
 * @hide
 */
public abstract class AndroidKeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOperation {

  public static class HmacSHA1 extends AndroidKeyStoreHmacSpi {
    public HmacSHA1() {
      super(KeymasterDefs.KM_DIGEST_SHA1);
    }
  }

  public static class HmacSHA224 extends AndroidKeyStoreHmacSpi {
    public HmacSHA224() {
      super(KeymasterDefs.KM_DIGEST_SHA_2_224);
    }
  }

  public static class HmacSHA256 extends AndroidKeyStoreHmacSpi {
    public HmacSHA256() {
      super(KeymasterDefs.KM_DIGEST_SHA_2_256);
    }
  }

  public static class HmacSHA384 extends AndroidKeyStoreHmacSpi {
    public HmacSHA384() {
      super(KeymasterDefs.KM_DIGEST_SHA_2_384);
    }
  }

  public static class HmacSHA512 extends AndroidKeyStoreHmacSpi {
    public HmacSHA512() {
      super(KeymasterDefs.KM_DIGEST_SHA_2_512);
    }
  }

  private final KeyStore mKeyStore = KeyStore.getInstance();
  private final int mKeymasterDigest;
  private final int mMacSizeBits;

  // Fields below are populated by engineInit and should be preserved after engineDoFinal.
  private AndroidKeyStoreSecretKey mKey;

  // Fields below are reset when engineDoFinal succeeds.
  private KeyStoreCryptoOperationChunkedStreamer mChunkedStreamer;
  private IBinder mOperationToken;
  private long mOperationHandle;

  protected AndroidKeyStoreHmacSpi(int keymasterDigest) {
    mKeymasterDigest = keymasterDigest;
    mMacSizeBits = KeymasterUtils.getDigestOutputSizeBits(keymasterDigest);
  }

  @Override
  protected int engineGetMacLength() {
    return (mMacSizeBits + 7) / 8;
  }

  @Override
  protected void engineInit(Key key, AlgorithmParameterSpec params)
      throws InvalidKeyException, InvalidAlgorithmParameterException {
    resetAll();

    boolean success = false;
    try {
      init(key, params);
      ensureKeystoreOperationInitialized();
      success = true;
    } finally {
      if (!success) {
        resetAll();
      }
    }
  }

  private void init(Key key, AlgorithmParameterSpec params)
      throws InvalidKeyException, InvalidAlgorithmParameterException {
    if (key == null) {
      throw new InvalidKeyException("key == null");
    } else if (!(key instanceof AndroidKeyStoreSecretKey)) {
      throw new InvalidKeyException("Only Android KeyStore secret keys supported. Key: " + key);
    }
    mKey = (AndroidKeyStoreSecretKey) key;

    if (params != null) {
      throw new InvalidAlgorithmParameterException("Unsupported algorithm parameters: " + params);
    }
  }

  private void resetAll() {
    mKey = null;
    IBinder operationToken = mOperationToken;
    if (operationToken != null) {
      mKeyStore.abort(operationToken);
    }
    mOperationToken = null;
    mOperationHandle = 0;
    mChunkedStreamer = null;
  }

  private void resetWhilePreservingInitState() {
    IBinder operationToken = mOperationToken;
    if (operationToken != null) {
      mKeyStore.abort(operationToken);
    }
    mOperationToken = null;
    mOperationHandle = 0;
    mChunkedStreamer = null;
  }

  @Override
  protected void engineReset() {
    resetWhilePreservingInitState();
  }

  private void ensureKeystoreOperationInitialized() throws InvalidKeyException {
    if (mChunkedStreamer != null) {
      return;
    }
    if (mKey == null) {
      throw new IllegalStateException("Not initialized");
    }

    KeymasterArguments keymasterArgs = new KeymasterArguments();
    keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_HMAC);
    keymasterArgs.addEnum(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest);
    keymasterArgs.addUnsignedInt(KeymasterDefs.KM_TAG_MAC_LENGTH, mMacSizeBits);

    OperationResult opResult =
        mKeyStore.begin(
            mKey.getAlias(),
            KeymasterDefs.KM_PURPOSE_SIGN,
            true,
            keymasterArgs,
            null, // no additional entropy needed for HMAC because it's deterministic
            mKey.getUid());

    if (opResult == null) {
      throw new KeyStoreConnectException();
    }

    // Store operation token and handle regardless of the error code returned by KeyStore to
    // ensure that the operation gets aborted immediately if the code below throws an exception.
    mOperationToken = opResult.token;
    mOperationHandle = opResult.operationHandle;

    // If necessary, throw an exception due to KeyStore operation having failed.
    InvalidKeyException e =
        KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit(
            mKeyStore, mKey, opResult.resultCode);
    if (e != null) {
      throw e;
    }

    if (mOperationToken == null) {
      throw new ProviderException("Keystore returned null operation token");
    }
    if (mOperationHandle == 0) {
      throw new ProviderException("Keystore returned invalid operation handle");
    }

    mChunkedStreamer =
        new KeyStoreCryptoOperationChunkedStreamer(
            new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(mKeyStore, mOperationToken));
  }

  @Override
  protected void engineUpdate(byte input) {
    engineUpdate(new byte[] {input}, 0, 1);
  }

  @Override
  protected void engineUpdate(byte[] input, int offset, int len) {
    try {
      ensureKeystoreOperationInitialized();
    } catch (InvalidKeyException e) {
      throw new ProviderException("Failed to reinitialize MAC", e);
    }

    byte[] output;
    try {
      output = mChunkedStreamer.update(input, offset, len);
    } catch (KeyStoreException e) {
      throw new ProviderException("Keystore operation failed", e);
    }
    if ((output != null) && (output.length != 0)) {
      throw new ProviderException("Update operation unexpectedly produced output");
    }
  }

  @Override
  protected byte[] engineDoFinal() {
    try {
      ensureKeystoreOperationInitialized();
    } catch (InvalidKeyException e) {
      throw new ProviderException("Failed to reinitialize MAC", e);
    }

    byte[] result;
    try {
      result =
          mChunkedStreamer.doFinal(
              null,
              0,
              0,
              null, // no signature provided -- this invocation will generate one
              null // no additional entropy needed -- HMAC is deterministic
              );
    } catch (KeyStoreException e) {
      throw new ProviderException("Keystore operation failed", e);
    }

    resetWhilePreservingInitState();
    return result;
  }

  @Override
  public void finalize() throws Throwable {
    try {
      IBinder operationToken = mOperationToken;
      if (operationToken != null) {
        mKeyStore.abort(operationToken);
      }
    } finally {
      super.finalize();
    }
  }

  @Override
  public long getOperationHandle() {
    return mOperationHandle;
  }
}