| /* |
| * Copyright (C) 2011 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.app.Dialog; |
| import android.app.Fragment; |
| import android.content.DialogInterface; |
| import android.net.http.SslCertificate; |
| import android.os.AsyncTask; |
| import android.os.Bundle; |
| import android.os.RemoteException; |
| import android.security.IKeyChainService; |
| import android.security.KeyChain; |
| import android.security.KeyChain.KeyChainConnection; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.widget.AdapterView; |
| import android.widget.BaseAdapter; |
| import android.widget.Button; |
| import android.widget.CheckBox; |
| import android.widget.FrameLayout; |
| import android.widget.ListView; |
| import android.widget.ProgressBar; |
| import android.widget.TabHost; |
| import android.widget.TextView; |
| import java.security.cert.CertificateEncodingException; |
| import java.security.cert.X509Certificate; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Set; |
| import org.apache.harmony.xnet.provider.jsse.TrustedCertificateStore; |
| |
| public class TrustedCredentialsSettings extends Fragment { |
| |
| private static final String TAG = "TrustedCredentialsSettings"; |
| |
| private enum Tab { |
| SYSTEM("system", |
| R.string.trusted_credentials_system_tab, |
| R.id.system_tab, |
| R.id.system_progress, |
| R.id.system_list, |
| true), |
| USER("user", |
| R.string.trusted_credentials_user_tab, |
| R.id.user_tab, |
| R.id.user_progress, |
| R.id.user_list, |
| false); |
| |
| private final String mTag; |
| private final int mLabel; |
| private final int mView; |
| private final int mProgress; |
| private final int mList; |
| private final boolean mCheckbox; |
| private Tab(String tag, int label, int view, int progress, int list, boolean checkbox) { |
| mTag = tag; |
| mLabel = label; |
| mView = view; |
| mProgress = progress; |
| mList = list; |
| mCheckbox = checkbox; |
| } |
| private Set<String> getAliases(TrustedCertificateStore store) { |
| switch (this) { |
| case SYSTEM: |
| return store.allSystemAliases(); |
| case USER: |
| return store.userAliases(); |
| } |
| throw new AssertionError(); |
| } |
| private boolean deleted(TrustedCertificateStore store, String alias) { |
| switch (this) { |
| case SYSTEM: |
| return !store.containsAlias(alias); |
| case USER: |
| return false; |
| } |
| throw new AssertionError(); |
| } |
| private int getButtonLabel(CertHolder certHolder) { |
| switch (this) { |
| case SYSTEM: |
| if (certHolder.mDeleted) { |
| return R.string.trusted_credentials_enable_label; |
| } |
| return R.string.trusted_credentials_disable_label; |
| case USER: |
| return R.string.trusted_credentials_remove_label; |
| } |
| throw new AssertionError(); |
| } |
| private int getButtonConfirmation(CertHolder certHolder) { |
| switch (this) { |
| case SYSTEM: |
| if (certHolder.mDeleted) { |
| return R.string.trusted_credentials_enable_confirmation; |
| } |
| return R.string.trusted_credentials_disable_confirmation; |
| case USER: |
| return R.string.trusted_credentials_remove_confirmation; |
| } |
| throw new AssertionError(); |
| } |
| private void postOperationUpdate(boolean ok, CertHolder certHolder) { |
| if (ok) { |
| if (certHolder.mTab.mCheckbox) { |
| certHolder.mDeleted = !certHolder.mDeleted; |
| } else { |
| certHolder.mAdapter.mCertHolders.remove(certHolder); |
| } |
| certHolder.mAdapter.notifyDataSetChanged(); |
| } else { |
| // bail, reload to reset to known state |
| certHolder.mAdapter.load(); |
| } |
| } |
| } |
| |
| // be careful not to use this on the UI thread since it is does file operations |
| private final TrustedCertificateStore mStore = new TrustedCertificateStore(); |
| |
| private TabHost mTabHost; |
| |
| @Override public View onCreateView( |
| LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) { |
| mTabHost = (TabHost) inflater.inflate(R.layout.trusted_credentials, parent, false); |
| mTabHost.setup(); |
| addTab(Tab.SYSTEM); |
| // TODO add Install button on Tab.USER to go to CertInstaller like KeyChainActivity |
| addTab(Tab.USER); |
| return mTabHost; |
| } |
| |
| private void addTab(Tab tab) { |
| TabHost.TabSpec systemSpec = mTabHost.newTabSpec(tab.mTag) |
| .setIndicator(getActivity().getString(tab.mLabel)) |
| .setContent(tab.mView); |
| mTabHost.addTab(systemSpec); |
| |
| ListView lv = (ListView) mTabHost.findViewById(tab.mList); |
| final TrustedCertificateAdapter adapter = new TrustedCertificateAdapter(tab); |
| lv.setAdapter(adapter); |
| lv.setOnItemClickListener(new AdapterView.OnItemClickListener() { |
| @Override public void onItemClick(AdapterView<?> parent, View view, int pos, long id) { |
| showCertDialog(adapter.getItem(pos)); |
| } |
| }); |
| } |
| |
| private class TrustedCertificateAdapter extends BaseAdapter { |
| private final List<CertHolder> mCertHolders = new ArrayList<CertHolder>(); |
| private final Tab mTab; |
| private TrustedCertificateAdapter(Tab tab) { |
| mTab = tab; |
| load(); |
| } |
| private void load() { |
| new AliasLoader().execute(); |
| } |
| @Override public int getCount() { |
| return mCertHolders.size(); |
| } |
| @Override public CertHolder getItem(int position) { |
| return mCertHolders.get(position); |
| } |
| @Override public long getItemId(int position) { |
| return position; |
| } |
| @Override public View getView(int position, View view, ViewGroup parent) { |
| ViewHolder holder; |
| if (view == null) { |
| LayoutInflater inflater = LayoutInflater.from(getActivity()); |
| view = inflater.inflate(R.layout.trusted_credential, parent, false); |
| holder = new ViewHolder(); |
| holder.mSubjectPrimaryView = (TextView) |
| view.findViewById(R.id.trusted_credential_subject_primary); |
| holder.mSubjectSecondaryView = (TextView) |
| view.findViewById(R.id.trusted_credential_subject_secondary); |
| holder.mCheckBox = (CheckBox) view.findViewById(R.id.trusted_credential_status); |
| view.setTag(holder); |
| } else { |
| holder = (ViewHolder) view.getTag(); |
| } |
| CertHolder certHolder = mCertHolders.get(position); |
| holder.mSubjectPrimaryView.setText(certHolder.mSubjectPrimary); |
| holder.mSubjectSecondaryView.setText(certHolder.mSubjectSecondary); |
| if (mTab.mCheckbox) { |
| holder.mCheckBox.setChecked(!certHolder.mDeleted); |
| holder.mCheckBox.setVisibility(View.VISIBLE); |
| } |
| return view; |
| }; |
| |
| private class AliasLoader extends AsyncTask<Void, Integer, List<CertHolder>> { |
| ProgressBar mProgressBar; |
| View mList; |
| @Override protected void onPreExecute() { |
| View content = mTabHost.getTabContentView(); |
| mProgressBar = (ProgressBar) content.findViewById(mTab.mProgress); |
| mList = content.findViewById(mTab.mList); |
| mProgressBar.setVisibility(View.VISIBLE); |
| mList.setVisibility(View.GONE); |
| } |
| @Override protected List<CertHolder> doInBackground(Void... params) { |
| Set<String> aliases = mTab.getAliases(mStore); |
| int max = aliases.size(); |
| int progress = 0; |
| List<CertHolder> certHolders = new ArrayList<CertHolder>(max); |
| for (String alias : aliases) { |
| X509Certificate cert = (X509Certificate) mStore.getCertificate(alias, true); |
| certHolders.add(new CertHolder(mStore, |
| TrustedCertificateAdapter.this, |
| mTab, |
| alias, |
| cert)); |
| publishProgress(++progress, max); |
| } |
| Collections.sort(certHolders); |
| return certHolders; |
| } |
| @Override protected void onProgressUpdate(Integer... progressAndMax) { |
| int progress = progressAndMax[0]; |
| int max = progressAndMax[1]; |
| if (max != mProgressBar.getMax()) { |
| mProgressBar.setMax(max); |
| } |
| mProgressBar.setProgress(progress); |
| } |
| @Override protected void onPostExecute(List<CertHolder> certHolders) { |
| mCertHolders.clear(); |
| mCertHolders.addAll(certHolders); |
| notifyDataSetChanged(); |
| View content = mTabHost.getTabContentView(); |
| mProgressBar.setVisibility(View.GONE); |
| mList.setVisibility(View.VISIBLE); |
| mProgressBar.setProgress(0); |
| } |
| } |
| } |
| |
| private static class CertHolder implements Comparable<CertHolder> { |
| private final TrustedCertificateStore mStore; |
| private final TrustedCertificateAdapter mAdapter; |
| private final Tab mTab; |
| private final String mAlias; |
| private final X509Certificate mX509Cert; |
| |
| private final SslCertificate mSslCert; |
| private final String mSubjectPrimary; |
| private final String mSubjectSecondary; |
| private boolean mDeleted; |
| |
| private CertHolder(TrustedCertificateStore store, |
| TrustedCertificateAdapter adapter, |
| Tab tab, |
| String alias, |
| X509Certificate x509Cert) { |
| mStore = store; |
| mAdapter = adapter; |
| mTab = tab; |
| mAlias = alias; |
| mX509Cert = x509Cert; |
| |
| mSslCert = new SslCertificate(x509Cert); |
| |
| String cn = mSslCert.getIssuedTo().getCName(); |
| String o = mSslCert.getIssuedTo().getOName(); |
| String ou = mSslCert.getIssuedTo().getUName(); |
| // if we have a O, use O as primary subject, secondary prefer CN over OU |
| // if we don't have an O, use CN as primary, empty secondary |
| // if we don't have O or CN, use DName as primary, empty secondary |
| if (!o.isEmpty()) { |
| if (!cn.isEmpty()) { |
| mSubjectPrimary = o; |
| mSubjectSecondary = cn; |
| } else { |
| mSubjectPrimary = o; |
| mSubjectSecondary = ou; |
| } |
| } else { |
| if (!cn.isEmpty()) { |
| mSubjectPrimary = cn; |
| mSubjectSecondary = ""; |
| } else { |
| mSubjectPrimary = mSslCert.getIssuedTo().getDName(); |
| mSubjectSecondary = ""; |
| } |
| } |
| mDeleted = mTab.deleted(mStore, mAlias); |
| } |
| @Override public int compareTo(CertHolder o) { |
| int primary = this.mSubjectPrimary.compareToIgnoreCase(o.mSubjectPrimary); |
| if (primary != 0) { |
| return primary; |
| } |
| return this.mSubjectSecondary.compareToIgnoreCase(o.mSubjectSecondary); |
| } |
| @Override public boolean equals(Object o) { |
| if (!(o instanceof CertHolder)) { |
| return false; |
| } |
| CertHolder other = (CertHolder) o; |
| return mAlias.equals(other.mAlias); |
| } |
| @Override public int hashCode() { |
| return mAlias.hashCode(); |
| } |
| } |
| |
| private static class ViewHolder { |
| private TextView mSubjectPrimaryView; |
| private TextView mSubjectSecondaryView; |
| private CheckBox mCheckBox; |
| } |
| |
| private void showCertDialog(final CertHolder certHolder) { |
| View view = certHolder.mSslCert.inflateCertificateView(getActivity()); |
| AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); |
| builder.setTitle(com.android.internal.R.string.ssl_certificate); |
| builder.setView(view); |
| builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { |
| @Override public void onClick(DialogInterface dialog, int id) { |
| dialog.dismiss(); |
| } |
| }); |
| final Dialog certDialog = builder.create(); |
| |
| ViewGroup body = (ViewGroup) view.findViewById(com.android.internal.R.id.body); |
| LayoutInflater inflater = LayoutInflater.from(getActivity()); |
| Button removeButton = (Button) inflater.inflate(R.layout.trusted_credential_details, |
| body, |
| false); |
| body.addView(removeButton); |
| removeButton.setText(certHolder.mTab.getButtonLabel(certHolder)); |
| removeButton.setOnClickListener(new View.OnClickListener() { |
| @Override public void onClick(View v) { |
| AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); |
| builder.setMessage(certHolder.mTab.getButtonConfirmation(certHolder)); |
| builder.setPositiveButton( |
| android.R.string.yes, new DialogInterface.OnClickListener() { |
| @Override public void onClick(DialogInterface dialog, int id) { |
| new AliasOperation(certHolder).execute(); |
| dialog.dismiss(); |
| certDialog.dismiss(); |
| } |
| }); |
| builder.setNegativeButton( |
| android.R.string.no, new DialogInterface.OnClickListener() { |
| @Override public void onClick(DialogInterface dialog, int id) { |
| dialog.cancel(); |
| } |
| }); |
| AlertDialog alert = builder.create(); |
| alert.show(); |
| } |
| }); |
| |
| certDialog.show(); |
| } |
| |
| private class AliasOperation extends AsyncTask<Void, Void, Boolean> { |
| private final CertHolder mCertHolder; |
| private AliasOperation(CertHolder certHolder) { |
| mCertHolder = certHolder; |
| } |
| @Override protected Boolean doInBackground(Void... params) { |
| try { |
| KeyChainConnection keyChainConnection = KeyChain.bind(getActivity()); |
| IKeyChainService service = keyChainConnection.getService(); |
| try { |
| if (mCertHolder.mDeleted) { |
| byte[] bytes = mCertHolder.mX509Cert.getEncoded(); |
| service.installCaCertificate(bytes); |
| return true; |
| } else { |
| return service.deleteCaCertificate(mCertHolder.mAlias); |
| } |
| } finally { |
| keyChainConnection.close(); |
| } |
| } catch (CertificateEncodingException e) { |
| return false; |
| } catch (IllegalStateException e) { |
| // used by installCaCertificate to report errors |
| return false; |
| } catch (RemoteException e) { |
| return false; |
| } catch (InterruptedException e) { |
| Thread.currentThread().interrupt(); |
| return false; |
| } |
| } |
| @Override protected void onPostExecute(Boolean ok) { |
| mCertHolder.mTab.postOperationUpdate(ok, mCertHolder); |
| } |
| } |
| } |