sdk doc change: Added KeyChain API Demo
Change-Id: Ib2b751a9de485359705b243d2c70e5f8f2c8ab3c
diff --git a/build/sdk.atree b/build/sdk.atree
index 038dafc..b00aa64 100644
--- a/build/sdk.atree
+++ b/build/sdk.atree
@@ -170,6 +170,7 @@
development/samples/Home samples/${PLATFORM_NAME}/Home
development/samples/HoneycombGallery samples/${PLATFORM_NAME}/HoneycombGallery
development/samples/JetBoy samples/${PLATFORM_NAME}/JetBoy
+development/samples/KeyChainDemo samples/${PLATFORM_NAME}/KeyChainDemo
development/samples/LunarLander samples/${PLATFORM_NAME}/LunarLander
development/samples/MultiResolution samples/${PLATFORM_NAME}/MultiResolution
development/samples/NotePad samples/${PLATFORM_NAME}/NotePad
diff --git a/samples/KeyChainDemo/Android.mk b/samples/KeyChainDemo/Android.mk
new file mode 100644
index 0000000..a607e95
--- /dev/null
+++ b/samples/KeyChainDemo/Android.mk
@@ -0,0 +1,16 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := samples
+
+# Only compile source java files in this apk.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := KeyChainDemo
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
+
+# Use the following include to make our test apk.
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/samples/KeyChainDemo/AndroidManifest.xml b/samples/KeyChainDemo/AndroidManifest.xml
new file mode 100644
index 0000000..e529309
--- /dev/null
+++ b/samples/KeyChainDemo/AndroidManifest.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2012 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.keychain"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="15" />
+
+ <uses-permission android:name="android.permission.INTERNET" />
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name" >
+ <activity
+ android:name="com.example.android.keychain.KeyChainDemoActivity"
+ android:label="@string/app_name"
+ android:launchMode="singleTop"
+ android:screenOrientation="portrait" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <service android:name="com.example.android.keychain.SecureWebServerService" />
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/samples/KeyChainDemo/_index.html b/samples/KeyChainDemo/_index.html
new file mode 100644
index 0000000..57e91b3
--- /dev/null
+++ b/samples/KeyChainDemo/_index.html
@@ -0,0 +1,50 @@
+<p>This is a demo application highlighting how to use the KeyChain APIs introduced in API Level 14.</p>
+
+<p>The source code for this demo app shows how to install a PKCS12 key chain stored in the assets folder,
+ grant permission to the app to use the installed key chain and finally display the certificate and
+ private key info. The app also has a simple implementation of a web server listening to requests
+ at an SSL socket using the same certificate in the key chain.
+</p>
+
+<p>The application includes the following key classes:</p>
+<ul>
+ <li><a href="src/com/example/android/keychain/KeyChainDemoActivity.html"><code>KeyChainDemoActivity</code></a>
+ — the main <code>Activity</code> that is used to install the key chain and start/stop
+ the web server. </li>
+ <li><a href="src/com/example/android/keychain/SecureWebServer.html"><code>SecureWebServer</code></a>
+ — a single thread web server listening at port 8080 for <code>https://localhost:8080</code> request</li>
+ <li><a href="src/com/example/android/keychain/SecureWebServerService.html"><code>SecureWebServerService</code></a>
+ — a <code>Service</code> that runs the web server in the foreground.</li>
+</ul>
+
+<p>If you are developing an application that uses the KeyChain APIs,
+ remember that the feature is supported only on Android 4.0 (API level 14) and
+ higher versions of the platform. To ensure that your application can only be
+ installed on devices that are running Android 4.0, remember to add the
+ following to the application's manifest:</p>
+<ul>
+ <li><code><uses-sdk android:minSdkVersion="14" /></code>, which
+ indicates to the Android platform that your application requires
+ Android 4.0 or higher. For more information, see <a
+ href="../../../guide/appendix/api-levels.html">API Levels</a> and the
+ documentation for the <a
+ href="../../../guide/topics/manifest/uses-sdk-element.html"><code><uses-sdk></code></a>
+ element.</li>
+</ul>
+
+<p>Note: Due to browser cache, you need to restart the browser completely before it can recognize
+ any keystore updates. The easiest way to do this is to dismiss the app in the Recent Apps list.
+</p>
+
+<p>
+ For more information about using the KeyChain API, see the
+ <a href="../../../reference/android/security/KeyChain.html">
+ <code>android.security.KeyChain</code></a>
+ documentation.
+</p>
+
+<img alt="" src="../images/KeyChainDemo1.png" />
+<img alt="" src="../images/KeyChainDemo2.png" />
+<img alt="" src="../images/KeyChainDemo3.png" />
+<img alt="" src="../images/KeyChainDemo4.png" />
+
diff --git a/samples/KeyChainDemo/assets/keychain.p12 b/samples/KeyChainDemo/assets/keychain.p12
new file mode 100644
index 0000000..66bd916
--- /dev/null
+++ b/samples/KeyChainDemo/assets/keychain.p12
Binary files differ
diff --git a/samples/KeyChainDemo/assets/training-prof.png b/samples/KeyChainDemo/assets/training-prof.png
new file mode 100644
index 0000000..ad91db2
--- /dev/null
+++ b/samples/KeyChainDemo/assets/training-prof.png
Binary files differ
diff --git a/samples/KeyChainDemo/res/drawable-hdpi/ic_launcher.png b/samples/KeyChainDemo/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..8074c4c
--- /dev/null
+++ b/samples/KeyChainDemo/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples/KeyChainDemo/res/drawable-ldpi/ic_launcher.png b/samples/KeyChainDemo/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..1095584
--- /dev/null
+++ b/samples/KeyChainDemo/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/samples/KeyChainDemo/res/drawable-mdpi/ic_launcher.png b/samples/KeyChainDemo/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/samples/KeyChainDemo/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples/KeyChainDemo/res/layout/main.xml b/samples/KeyChainDemo/res/layout/main.xml
new file mode 100644
index 0000000..c968c08
--- /dev/null
+++ b/samples/KeyChainDemo/res/layout/main.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2012 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_margin="5dp"
+ android:text="@string/keychain_label" />
+
+ <Button
+ android:id="@+id/keychain_button"
+ android:layout_width="100dp"
+ android:layout_height="wrap_content"
+ android:layout_margin="5dp"
+ android:text="@string/keychain_install" />
+
+ <TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_margin="5dp"
+ android:text="@string/server_label" />
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <Button
+ android:id="@+id/server_button"
+ android:layout_width="100dp"
+ android:layout_height="wrap_content"
+ android:layout_margin="5dp"
+ android:text="@string/server_start" />
+
+ <Button
+ android:id="@+id/test_ssl_button"
+ android:layout_width="100dp"
+ android:layout_height="wrap_content"
+ android:layout_margin="5dp"
+ android:text="@string/test_ssl" />
+ </LinearLayout>
+
+ <TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_margin="5dp"
+ android:text="@string/cert_label" />
+
+ <TextView
+ android:id="@+id/cert"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_margin="5dp"
+ android:gravity="left" />
+
+ <TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_margin="5dp"
+ android:text="@string/private_key_label" />
+
+ <ScrollView
+ android:layout_width="fill_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1" >
+
+ <TextView
+ android:id="@+id/private_key"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_margin="5dp"
+ android:gravity="left" />
+ </ScrollView>
+
+</LinearLayout>
diff --git a/samples/KeyChainDemo/res/values/strings.xml b/samples/KeyChainDemo/res/values/strings.xml
new file mode 100644
index 0000000..748f698
--- /dev/null
+++ b/samples/KeyChainDemo/res/values/strings.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2012 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.
+-->
+<resources>
+
+ <string name="app_name">KeyChainDemo</string>
+ <string name="cert_label">Certificate</string>
+ <string name="private_key_label">Private Key</string>
+ <string name="keychain_label">Key Chain Installation</string>
+ <string name="keychain_install">Install</string>
+ <string name="keychain_installed">Installed</string>
+ <string name="server_label">Simple SSL Web Server</string>
+ <string name="server_start">Start</string>
+ <string name="server_stop">Stop</string>
+ <string name="test_ssl">Test</string>
+ <string name="ticker_text">Starting KeyChainDemo SSL Server</string>
+ <string name="notification_title">KeyChainDemo SSL Server</string>
+ <string name="notification_message">Click to stop the server</string>
+
+</resources>
\ No newline at end of file
diff --git a/samples/KeyChainDemo/src/com/example/android/keychain/KeyChainDemoActivity.java b/samples/KeyChainDemo/src/com/example/android/keychain/KeyChainDemoActivity.java
new file mode 100644
index 0000000..a2028b8
--- /dev/null
+++ b/samples/KeyChainDemo/src/com/example/android/keychain/KeyChainDemoActivity.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright 2012 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.example.android.keychain;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.security.KeyChain;
+import android.security.KeyChainAliasCallback;
+import android.security.KeyChainException;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.TextView;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+
+public class KeyChainDemoActivity extends Activity implements
+ KeyChainAliasCallback {
+
+ /**
+ * The file name of the PKCS12 file used
+ */
+ public static final String PKCS12_FILENAME = "keychain.p12";
+
+ /**
+ * The pass phrase of the PKCS12 file
+ */
+ public static final String PKCS12_PASSWORD = "changeit";
+
+ /**
+ * Intent extra name to indicate to stop server
+ */
+ public static final String EXTRA_STOP_SERVER = "stop_server";
+
+ // Log tag for this class
+ private static final String TAG = "KeyChainApiActivity";
+
+ // Alias for certificate
+ private static final String DEFAULT_ALIAS = "My Key Chain";
+
+ // Name of the application preference
+ private static final String KEYCHAIN_PREF = "keychain";
+
+ // Name of preference name that saves the alias
+ private static final String KEYCHAIN_PREF_ALIAS = "alias";
+
+ // Request code used when starting the activity using the KeyChain install
+ // intent
+ private static final int INSTALL_KEYCHAIN_CODE = 1;
+
+ // Test SSL URL
+ private static final String TEST_SSL_URL = "https://localhost:8080";
+
+ // Button to start/stop the simple SSL web server
+ private Button serverButton;
+
+ // Button to install the key chain
+ private Button keyChainButton;
+
+ // Button to launch the browser for testing https://localhost:8080
+ private Button testSslButton;
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Set the view using the main.xml layout
+ setContentView(R.layout.main);
+
+ // Check whether the key chain is installed or not. This takes time and
+ // should be done in another thread other than the main thread.
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ if (isKeyChainAccessible()) {
+ // Key chain installed. Disable the install button and print
+ // the key chain information
+ disableKeyChainButton();
+ printInfo();
+ } else {
+ Log.d(TAG, "Key Chain is not accessible");
+ }
+ }
+ }).start();
+
+ // Setup the key chain installation button
+ keyChainButton = (Button) findViewById(R.id.keychain_button);
+ keyChainButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ installPkcs12();
+ }
+ });
+
+ // Setup the simple SSL web server start/stop button
+ serverButton = (Button) findViewById(R.id.server_button);
+ serverButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (serverButton.getText().equals(
+ getResources().getString(R.string.server_start))) {
+ serverButton.setText(R.string.server_stop);
+ startServer();
+ } else {
+ serverButton.setText(R.string.server_start);
+ stopServer();
+ }
+ }
+ });
+
+ // Setup the test SSL page button
+ testSslButton = (Button) findViewById(R.id.test_ssl_button);
+ testSslButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent i = new Intent(Intent.ACTION_VIEW, Uri
+ .parse(TEST_SSL_URL));
+ startActivity(i);
+ }
+ });
+ }
+
+ /**
+ * This will be called when the user click on the notification to stop the
+ * SSL server
+ */
+ @Override
+ protected void onNewIntent(Intent intent) {
+ Log.d(TAG, "In onNewIntent()");
+ super.onNewIntent(intent);
+ boolean isStopServer = intent.getBooleanExtra(EXTRA_STOP_SERVER, false);
+ if (isStopServer) {
+ serverButton.setText(R.string.server_start);
+ stopServer();
+ }
+ }
+
+ /**
+ * This implements the KeyChainAliasCallback
+ */
+ @Override
+ public void alias(String alias) {
+ if (alias != null) {
+ setAlias(alias); // Set the alias in the application preference
+ disableKeyChainButton();
+ printInfo();
+ } else {
+ Log.d(TAG, "User hit Disallow");
+ }
+ }
+
+ /**
+ * This method returns the alias of the key chain from the application
+ * preference
+ *
+ * @return The alias of the key chain
+ */
+ private String getAlias() {
+ SharedPreferences pref = getSharedPreferences(KEYCHAIN_PREF,
+ MODE_PRIVATE);
+ return pref.getString(KEYCHAIN_PREF_ALIAS, DEFAULT_ALIAS);
+ }
+
+ /**
+ * This method sets the alias of the key chain to the application preference
+ */
+ private void setAlias(String alias) {
+ SharedPreferences pref = getSharedPreferences(KEYCHAIN_PREF,
+ MODE_PRIVATE);
+ Editor editor = pref.edit();
+ editor.putString(KEYCHAIN_PREF_ALIAS, alias);
+ editor.commit();
+ }
+
+ /**
+ * This method prints the key chain information.
+ */
+ private void printInfo() {
+ String alias = getAlias();
+ X509Certificate[] certs = getCertificateChain(alias);
+ final PrivateKey privateKey = getPrivateKey(alias);
+ final StringBuffer sb = new StringBuffer();
+ for (X509Certificate cert : certs) {
+ sb.append(cert.getIssuerDN());
+ sb.append("\n");
+ }
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ TextView certTv = (TextView) findViewById(R.id.cert);
+ TextView privateKeyTv = (TextView) findViewById(R.id.private_key);
+ certTv.setText(sb.toString());
+ privateKeyTv.setText(privateKey.getFormat() + ":" + privateKey);
+ }
+ });
+ }
+
+ /**
+ * This method will launch an intent to install the key chain
+ */
+ private void installPkcs12() {
+ try {
+ BufferedInputStream bis = new BufferedInputStream(getAssets().open(
+ PKCS12_FILENAME));
+ byte[] keychain = new byte[bis.available()];
+ bis.read(keychain);
+
+ Intent installIntent = KeyChain.createInstallIntent();
+ installIntent.putExtra(KeyChain.EXTRA_PKCS12, keychain);
+ installIntent.putExtra(KeyChain.EXTRA_NAME, DEFAULT_ALIAS);
+ startActivityForResult(installIntent, INSTALL_KEYCHAIN_CODE);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == INSTALL_KEYCHAIN_CODE) {
+ switch (resultCode) {
+ case Activity.RESULT_OK:
+ chooseCert();
+ break;
+ default:
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+ }
+
+ private void chooseCert() {
+ KeyChain.choosePrivateKeyAlias(this, this, // Callback
+ new String[] {}, // Any key types.
+ null, // Any issuers.
+ "localhost", // Any host
+ -1, // Any port
+ DEFAULT_ALIAS);
+ }
+
+ private X509Certificate[] getCertificateChain(String alias) {
+ try {
+ return KeyChain.getCertificateChain(this, alias);
+ } catch (KeyChainException e) {
+ e.printStackTrace();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ private PrivateKey getPrivateKey(String alias) {
+ try {
+ return KeyChain.getPrivateKey(this, alias);
+ } catch (KeyChainException e) {
+ e.printStackTrace();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * This method checks if the key chain is installed
+ *
+ * @return true if the key chain is not installed or allowed
+ */
+ private boolean isKeyChainAccessible() {
+ return getCertificateChain(getAlias()) != null
+ && getPrivateKey(getAlias()) != null;
+ }
+
+ /**
+ * This method starts the background service of the simple SSL web server
+ */
+ private void startServer() {
+ Intent secureWebServerIntent = new Intent(this,
+ SecureWebServerService.class);
+ startService(secureWebServerIntent);
+ }
+
+ /**
+ * This method stops the background service of the simple SSL web server
+ */
+ private void stopServer() {
+ Intent secureWebServerIntent = new Intent(this,
+ SecureWebServerService.class);
+ stopService(secureWebServerIntent);
+ }
+
+ /**
+ * This is a convenient method to disable the key chain install button
+ */
+ private void disableKeyChainButton() {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ keyChainButton.setText(R.string.keychain_installed);
+ keyChainButton.setEnabled(false);
+ }
+ });
+ }
+
+}
diff --git a/samples/KeyChainDemo/src/com/example/android/keychain/SecureWebServer.java b/samples/KeyChainDemo/src/com/example/android/keychain/SecureWebServer.java
new file mode 100644
index 0000000..8f84000
--- /dev/null
+++ b/samples/KeyChainDemo/src/com/example/android/keychain/SecureWebServer.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2012 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.example.android.keychain;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.net.Socket;
+import java.security.KeyStore;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLServerSocketFactory;
+
+import android.content.Context;
+import android.util.Base64;
+import android.util.Log;
+
+public class SecureWebServer {
+
+ // Log tag for this class
+ private static final String TAG = "SecureWebServer";
+
+ // File name of the image used in server response
+ private static final String EMBEDDED_IMAGE_FILENAME = "training-prof.png";
+
+ private SSLServerSocketFactory sssf;
+ private SSLServerSocket sss;
+
+ // A flag to control whether the web server should be kept running
+ private boolean isRunning = true;
+
+ // The base64 encoded image string used as an embedded image
+ private final String base64Image;
+
+ /**
+ * WebServer constructor.
+ */
+ public SecureWebServer(Context ctx) {
+ try {
+ // Get an SSL context using the TLS protocol
+ SSLContext sslContext = SSLContext.getInstance("TLS");
+
+ // Get a key manager factory using the default algorithm
+ KeyManagerFactory kmf = KeyManagerFactory
+ .getInstance(KeyManagerFactory.getDefaultAlgorithm());
+
+ // Load the PKCS12 key chain
+ KeyStore ks = KeyStore.getInstance("PKCS12");
+ FileInputStream fis = ctx.getAssets()
+ .openFd(KeyChainDemoActivity.PKCS12_FILENAME)
+ .createInputStream();
+ ks.load(fis, KeyChainDemoActivity.PKCS12_PASSWORD.toCharArray());
+ kmf.init(ks, KeyChainDemoActivity.PKCS12_PASSWORD.toCharArray());
+
+ // Initialize the SSL context
+ sslContext.init(kmf.getKeyManagers(), null, null);
+
+ // Create the SSL server socket factory
+ sssf = sslContext.getServerSocketFactory();
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ // Create the base64 image string used in the server response
+ base64Image = createBase64Image(ctx);
+ }
+
+ /**
+ * This method starts the web server listening to the port 8080
+ */
+ protected void start() {
+
+ new Thread(new Runnable() {
+
+ @Override
+ public void run() {
+ Log.d(TAG, "Secure Web Server is starting up on port 8080");
+ try {
+ // Create the secure server socket
+ sss = (SSLServerSocket) sssf.createServerSocket(8080);
+ } catch (Exception e) {
+ System.out.println("Error: " + e);
+ return;
+ }
+
+ Log.d(TAG, "Waiting for connection");
+ while (isRunning) {
+ try {
+ // Wait for an SSL connection
+ Socket socket = sss.accept();
+
+ // Got a connection
+ Log.d(TAG, "Connected, sending data.");
+
+ BufferedReader in = new BufferedReader(
+ new InputStreamReader(socket.getInputStream()));
+ PrintWriter out = new PrintWriter(socket
+ .getOutputStream());
+
+ // Read the data until a blank line is reached which
+ // signifies the end of the client HTTP headers
+ String str = ".";
+ while (!str.equals(""))
+ str = in.readLine();
+
+ // Send a HTTP response
+ out.println("HTTP/1.0 200 OK");
+ out.println("Content-Type: text/html");
+ out.println("Server: Android KeyChainiDemo SSL Server");
+ // this blank line signals the end of the headers
+ out.println("");
+ // Send the HTML page
+ out.println("<H1>Welcome to Android!</H1>");
+ // Add an embedded Android image
+ out.println("<img src='data:image/png;base64," + base64Image + "'/>");
+ out.flush();
+ socket.close();
+ } catch (Exception e) {
+ Log.d(TAG, "Error: " + e);
+ }
+ }
+ }
+ }).start();
+
+ }
+
+ /**
+ * This method stops the SSL web server
+ */
+ protected void stop() {
+ try {
+ // Break out from the infinite while loop in start()
+ isRunning = false;
+
+ // Close the socket
+ if (sss != null) {
+ sss.close();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * This method reads a binary image from the assets folder and returns the
+ * base64 encoded image string.
+ *
+ * @param ctx The service this web server is running in.
+ * @return String The base64 encoded image string or "" if there is an
+ * exception
+ */
+ private String createBase64Image(Context ctx) {
+ BufferedInputStream bis;
+ try {
+ bis = new BufferedInputStream(ctx.getAssets().open(EMBEDDED_IMAGE_FILENAME));
+ byte[] embeddedImage = new byte[bis.available()];
+ bis.read(embeddedImage);
+ return Base64.encodeToString(embeddedImage, Base64.DEFAULT);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return "";
+ }
+
+}
diff --git a/samples/KeyChainDemo/src/com/example/android/keychain/SecureWebServerService.java b/samples/KeyChainDemo/src/com/example/android/keychain/SecureWebServerService.java
new file mode 100644
index 0000000..74abb06
--- /dev/null
+++ b/samples/KeyChainDemo/src/com/example/android/keychain/SecureWebServerService.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2012 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.example.android.keychain;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+
+public class SecureWebServerService extends Service {
+
+ // Log tag for this class
+ private static final String TAG = "SecureWebServerService";
+
+ // A special ID assigned to this on-going notification.
+ private static final int ONGOING_NOTIFICATION = 1248;
+
+ // A handle to the simple SSL web server
+ private SecureWebServer sws;
+
+ /**
+ * Start the SSL web server and set an on-going notification
+ */
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ sws = new SecureWebServer(this);
+ sws.start();
+ createNotification();
+ }
+
+ /**
+ * Stop the SSL web server and remove the on-going notification
+ */
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ sws.stop();
+ stopForeground(true);
+ }
+
+ /**
+ * Return null as there is nothing to bind
+ */
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ /**
+ * Create an on-going notification. It will stop the server when the user
+ * clicks on the notification.
+ */
+ private void createNotification() {
+ Log.d(TAG, "Create an ongoing notification");
+ Intent notificationIntent = new Intent(this,
+ KeyChainDemoActivity.class);
+ notificationIntent.putExtra(KeyChainDemoActivity.EXTRA_STOP_SERVER,
+ true);
+ PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
+ notificationIntent, 0);
+ Notification notification = new Notification.Builder(this).
+ setContentTitle(getText(R.string.notification_title)).
+ setContentText(getText(R.string.notification_message)).
+ setSmallIcon(android.R.drawable.ic_media_play).
+ setTicker(getText(R.string.ticker_text)).
+ setOngoing(true).
+ setContentIntent(pendingIntent).
+ getNotification();
+ startForeground(ONGOING_NOTIFICATION, notification);
+ }
+
+}