Merge "Check and fail early if requested wallpaper size exceeds maximum texture size."
diff --git a/cmds/content/src/com/android/commands/content/Content.java b/cmds/content/src/com/android/commands/content/Content.java
index 787fbdb..ae39d24 100644
--- a/cmds/content/src/com/android/commands/content/Content.java
+++ b/cmds/content/src/com/android/commands/content/Content.java
@@ -96,7 +96,7 @@
+ " [--projection <PROJECTION>] [--where <WHERE>] [--sort <SORT_ORDER>]\n"
+ " <PROJECTION> is a list of colon separated column names and is formatted:\n"
+ " <COLUMN_NAME>[:<COLUMN_NAME>...]\n"
- + " <SORT_OREDER> is the order in which rows in the result should be sorted.\n"
+ + " <SORT_ORDER> is the order in which rows in the result should be sorted.\n"
+ " Example:\n"
+ " # Select \"name\" and \"value\" columns from secure settings where \"name\" is "
+ "equal to \"new_setting\" and sort the result by name in ascending order.\n"
diff --git a/core/java/android/util/FloatMath.java b/core/java/android/util/FloatMath.java
index 9556223..0ffd5bd 100644
--- a/core/java/android/util/FloatMath.java
+++ b/core/java/android/util/FloatMath.java
@@ -17,12 +17,10 @@
package android.util;
/**
- * Math routines similar to those found in {@link java.lang.Math}. Performs
- * computations on {@code float} values directly without incurring the overhead
- * of conversions to and from {@code double}.
- *
- * <p>On one platform, {@code FloatMath.sqrt(100)} executes in one third of the
- * time required by {@code java.lang.Math.sqrt(100)}.</p>
+ * Math routines similar to those found in {@link java.lang.Math}. On
+ * versions of Android with a JIT, these are significantly slower than
+ * the equivalent {@code Math} functions, which should be used in preference
+ * to these.
*/
public class FloatMath {
diff --git a/core/java/android/util/Patterns.java b/core/java/android/util/Patterns.java
index 152827d..9522112 100644
--- a/core/java/android/util/Patterns.java
+++ b/core/java/android/util/Patterns.java
@@ -169,10 +169,10 @@
* </ul>
*/
public static final Pattern PHONE
- = Pattern.compile( // sdd = space, dot, or dash
- "(\\+[0-9]+[\\- \\.]*)?" // +<digits><sdd>*
- + "(\\([0-9]+\\)[\\- \\.]*)?" // (<digits>)<sdd>*
- + "([0-9][0-9\\- \\.][0-9\\- \\.]+[0-9])"); // <digit><digit|sdd>+<digit>
+ = Pattern.compile( // sdd = space, dot, or dash
+ "(\\+[0-9]+[\\- \\.]*)?" // +<digits><sdd>*
+ + "(\\([0-9]+\\)[\\- \\.]*)?" // (<digits>)<sdd>*
+ + "([0-9][0-9\\- \\.]+[0-9])"); // <digit><digit|sdd>+<digit>
/**
* Convenience method to take all of the non-null matching groups in a
diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java
index ae56e6b..7154f95 100644
--- a/core/java/android/webkit/WebViewClassic.java
+++ b/core/java/android/webkit/WebViewClassic.java
@@ -1258,6 +1258,40 @@
mAutoFillData = new WebViewCore.AutoFillData();
mEditTextScroller = new Scroller(context);
+
+ // Calculate channel distance
+ calculateChannelDistance(context);
+ }
+
+ /**
+ * Calculate sChannelDistance based on the screen information.
+ * @param context A Context object used to access application assets.
+ */
+ private void calculateChannelDistance(Context context) {
+ // The channel distance is adjusted for density and screen size
+ final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
+ final double screenSize = Math.hypot((double)(metrics.widthPixels/metrics.densityDpi),
+ (double)(metrics.heightPixels/metrics.densityDpi));
+ if (screenSize < 3.0) {
+ sChannelDistance = 16;
+ } else if (screenSize < 5.0) {
+ sChannelDistance = 22;
+ } else if (screenSize < 7.0) {
+ sChannelDistance = 28;
+ } else {
+ sChannelDistance = 34;
+ }
+ sChannelDistance = (int)(sChannelDistance * metrics.density);
+ if (sChannelDistance < 16) sChannelDistance = 16;
+
+ if (DebugFlags.WEB_VIEW) {
+ Log.v(LOGTAG, "sChannelDistance : " + sChannelDistance
+ + ", density : " + metrics.density
+ + ", screenSize : " + screenSize
+ + ", metrics.heightPixels : " + metrics.heightPixels
+ + ", metrics.widthPixels : " + metrics.widthPixels
+ + ", metrics.densityDpi : " + metrics.densityDpi);
+ }
}
// WebViewProvider bindings
@@ -5715,32 +5749,13 @@
}
return mWebViewPrivate.super_dispatchKeyEvent(event);
}
-
- /*
- * Here is the snap align logic:
- * 1. If it starts nearly horizontally or vertically, snap align;
- * 2. If there is a dramitic direction change, let it go;
- *
- * Adjustable parameters. Angle is the radians on a unit circle, limited
- * to quadrant 1. Values range from 0f (horizontal) to PI/2 (vertical)
- */
- private static final float HSLOPE_TO_START_SNAP = .25f;
- private static final float HSLOPE_TO_BREAK_SNAP = .4f;
- private static final float VSLOPE_TO_START_SNAP = 1.25f;
- private static final float VSLOPE_TO_BREAK_SNAP = .95f;
- /*
- * These values are used to influence the average angle when entering
- * snap mode. If is is the first movement entering snap, we set the average
- * to the appropriate ideal. If the user is entering into snap after the
- * first movement, then we average the average angle with these values.
- */
- private static final float ANGLE_VERT = 2f;
- private static final float ANGLE_HORIZ = 0f;
- /*
- * The modified moving average weight.
- * Formula: MAV[t]=MAV[t-1] + (P[t]-MAV[t-1])/n
- */
- private static final float MMA_WEIGHT_N = 5;
+
+ private static final int SNAP_BOUND = 16;
+ private static int sChannelDistance = 16;
+ private int mFirstTouchX = -1; // the first touched point
+ private int mFirstTouchY = -1;
+ private int mDistanceX = 0;
+ private int mDistanceY = 0;
private boolean inFullScreenMode() {
return mFullScreenHolder != null;
@@ -5830,12 +5845,6 @@
}
}
- private float calculateDragAngle(int dx, int dy) {
- dx = Math.abs(dx);
- dy = Math.abs(dy);
- return (float) Math.atan2(dy, dx);
- }
-
/*
* Common code for single touch and multi-touch.
* (x, y) denotes current focus point, which is the touch point for single touch
@@ -5861,6 +5870,12 @@
switch (action) {
case MotionEvent.ACTION_DOWN: {
mConfirmMove = false;
+
+ // Channel Scrolling
+ mFirstTouchX = x;
+ mFirstTouchY = y;
+ mDistanceX = mDistanceY = 0;
+
if (!mEditTextScroller.isFinished()) {
mEditTextScroller.abortAnimation();
}
@@ -5998,20 +6013,16 @@
break;
}
- // Only lock dragging to one axis if we don't have a scale in progress.
- // Scaling implies free-roaming movement. Note this is only ever a question
- // if mZoomManager.supportsPanDuringZoom() is true.
- mAverageAngle = calculateDragAngle(deltaX, deltaY);
- if (detector == null || !detector.isInProgress()) {
- // if it starts nearly horizontal or vertical, enforce it
- if (mAverageAngle < HSLOPE_TO_START_SNAP) {
- mSnapScrollMode = SNAP_X;
- mSnapPositive = deltaX > 0;
- mAverageAngle = ANGLE_HORIZ;
- } else if (mAverageAngle > VSLOPE_TO_START_SNAP) {
+ if ((detector == null || !detector.isInProgress())
+ && SNAP_NONE == mSnapScrollMode) {
+ int ax = Math.abs(x - mFirstTouchX);
+ int ay = Math.abs(y - mFirstTouchY);
+ if (ax < SNAP_BOUND && ay < SNAP_BOUND) {
+ break;
+ } else if (ax < SNAP_BOUND) {
mSnapScrollMode = SNAP_Y;
- mSnapPositive = deltaY > 0;
- mAverageAngle = ANGLE_VERT;
+ } else if (ay < SNAP_BOUND) {
+ mSnapScrollMode = SNAP_X;
}
}
@@ -6030,31 +6041,21 @@
if (deltaX == 0 && deltaY == 0) {
keepScrollBarsVisible = true;
} else {
- mAverageAngle +=
- (calculateDragAngle(deltaX, deltaY) - mAverageAngle)
- / MMA_WEIGHT_N;
- if (mSnapScrollMode != SNAP_NONE) {
- if (mSnapScrollMode == SNAP_Y) {
- // radical change means getting out of snap mode
- if (mAverageAngle < VSLOPE_TO_BREAK_SNAP) {
- mSnapScrollMode = SNAP_NONE;
- }
- }
+ if (mSnapScrollMode == SNAP_X || mSnapScrollMode == SNAP_Y) {
+ mDistanceX += Math.abs(deltaX);
+ mDistanceY += Math.abs(deltaY);
if (mSnapScrollMode == SNAP_X) {
- // radical change means getting out of snap mode
- if (mAverageAngle > HSLOPE_TO_BREAK_SNAP) {
+ if (mDistanceY > sChannelDistance) {
mSnapScrollMode = SNAP_NONE;
- }
+ } else if (mDistanceX > sChannelDistance) {
+ mDistanceX = mDistanceY = 0;
}
} else {
- if (mAverageAngle < HSLOPE_TO_START_SNAP) {
- mSnapScrollMode = SNAP_X;
- mSnapPositive = deltaX > 0;
- mAverageAngle = (mAverageAngle + ANGLE_HORIZ) / 2;
- } else if (mAverageAngle > VSLOPE_TO_START_SNAP) {
- mSnapScrollMode = SNAP_Y;
- mSnapPositive = deltaY > 0;
- mAverageAngle = (mAverageAngle + ANGLE_VERT) / 2;
+ if (mDistanceX > sChannelDistance) {
+ mSnapScrollMode = SNAP_NONE;
+ } else if (mDistanceY > sChannelDistance) {
+ mDistanceX = mDistanceY = 0;
+ }
}
}
if (mSnapScrollMode != SNAP_NONE) {
@@ -6089,6 +6090,7 @@
break;
}
case MotionEvent.ACTION_UP: {
+ mFirstTouchX = mFirstTouchY = -1;
if (mIsEditingText && mSelectionStarted) {
endScrollEdit();
mPrivateHandler.sendEmptyMessageDelayed(SCROLL_HANDLE_INTO_VIEW,
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 69e3177..4436fbb 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -2429,7 +2429,9 @@
View selectedView = getSelectedView();
int selectedPos = mSelectedPosition;
- int nextSelectedPosition = lookForSelectablePositionOnScreen(direction);
+ int nextSelectedPosition = (direction == View.FOCUS_DOWN) ?
+ lookForSelectablePosition(selectedPos + 1, true) :
+ lookForSelectablePosition(selectedPos - 1, false);
int amountToScroll = amountToScroll(direction, nextSelectedPosition);
// if we are moving focus, we may OVERRIDE the default behavior
@@ -2641,14 +2643,18 @@
final int listBottom = getHeight() - mListPadding.bottom;
final int listTop = mListPadding.top;
- final int numChildren = getChildCount();
+ int numChildren = getChildCount();
if (direction == View.FOCUS_DOWN) {
int indexToMakeVisible = numChildren - 1;
if (nextSelectedPosition != INVALID_POSITION) {
indexToMakeVisible = nextSelectedPosition - mFirstPosition;
}
-
+ while (numChildren <= indexToMakeVisible) {
+ // Child to view is not attached yet.
+ addViewBelow(getChildAt(numChildren - 1), mFirstPosition + numChildren - 1);
+ numChildren++;
+ }
final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
final View viewToMakeVisible = getChildAt(indexToMakeVisible);
@@ -2682,6 +2688,12 @@
if (nextSelectedPosition != INVALID_POSITION) {
indexToMakeVisible = nextSelectedPosition - mFirstPosition;
}
+ while (indexToMakeVisible < 0) {
+ // Child to view is not attached yet.
+ addViewAbove(getChildAt(0), mFirstPosition);
+ mFirstPosition--;
+ indexToMakeVisible = nextSelectedPosition - mFirstPosition;
+ }
final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
final View viewToMakeVisible = getChildAt(indexToMakeVisible);
int goalTop = listTop;
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 6724f36..6e21a11 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -159,7 +159,8 @@
name = line + name_pos;
nameLen = strlen(name);
- if (strstr(name, "[heap]") == name) {
+ if ((strstr(name, "[heap]") == name) ||
+ (strstr(name, "/dev/ashmem/libc malloc") == name)) {
whichHeap = HEAP_NATIVE;
} else if (strstr(name, "/dev/ashmem/dalvik-") == name) {
whichHeap = HEAP_DALVIK;
@@ -177,7 +178,8 @@
whichHeap = HEAP_APK;
} else if (nameLen > 4 && strcmp(name+nameLen-4, ".ttf") == 0) {
whichHeap = HEAP_TTF;
- } else if (nameLen > 4 && strcmp(name+nameLen-4, ".dex") == 0) {
+ } else if ((nameLen > 4 && strcmp(name+nameLen-4, ".dex") == 0) ||
+ (nameLen > 5 && strcmp(name+nameLen-5, ".odex") == 0)) {
whichHeap = HEAP_DEX;
} else if (nameLen > 0) {
whichHeap = HEAP_UNKNOWN_MAP;
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/AccessPointParserHelper.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/AccessPointParserHelper.java
index a6057de..0461c0b 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/AccessPointParserHelper.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/AccessPointParserHelper.java
@@ -28,6 +28,7 @@
import android.net.wifi.WifiConfiguration.IpAssignment;
import android.net.wifi.WifiConfiguration.KeyMgmt;
import android.net.wifi.WifiConfiguration.ProxySettings;
+import android.net.wifi.WifiEnterpriseConfig;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.RouteInfo;
@@ -67,7 +68,6 @@
* networkprefixlength.
*/
public class AccessPointParserHelper {
- private static final String KEYSTORE_SPACE = "keystore://";
private static final String TAG = "AccessPointParserHelper";
static final int NONE = 0;
static final int WEP = 1;
@@ -212,14 +212,11 @@
config.allowedKeyManagement.set(KeyMgmt.WPA_EAP);
config.allowedKeyManagement.set(KeyMgmt.IEEE8021X);
// Initialize other fields.
- config.phase2.setValue("");
- config.ca_cert.setValue("");
- config.client_cert.setValue("");
- config.engine.setValue("");
- config.engine_id.setValue("");
- config.key_id.setValue("");
- config.identity.setValue("");
- config.anonymous_identity.setValue("");
+ config.enterpriseConfig.setPhase2Method(WifiEnterpriseConfig.Phase2.NONE);
+ config.enterpriseConfig.setCaCertificateAlias("");
+ config.enterpriseConfig.setClientCertificateAlias("");
+ config.enterpriseConfig.setIdentity("");
+ config.enterpriseConfig.setAnonymousIdentity("");
break;
default:
throw new SAXException();
@@ -246,7 +243,7 @@
config.preSharedKey = '"' + passwordStr + '"';
}
} else if (securityType == EAP) {
- config.password.setValue(passwordStr);
+ config.enterpriseConfig.setPassword(passwordStr);
} else {
throw new SAXException();
}
@@ -257,33 +254,46 @@
if (!validateEapValue(eapValue)) {
throw new SAXException();
}
- config.eap.setValue(eapValue);
+ if (eapValue.equals("TLS")) {
+ config.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+ } else if (eapValue.equals("TTLS")) {
+ config.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TTLS);
+ } else if (eapValue.equals("PEAP")) {
+ config.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.PEAP);
+ }
eap = false;
}
if (phase2) {
String phase2Value = new String(ch, start, length);
- config.phase2.setValue("auth=" + phase2Value);
+ if (phase2Value.equals("PAP")) {
+ config.enterpriseConfig.setPhase2Method(WifiEnterpriseConfig.Phase2.PAP);
+ } else if (phase2Value.equals("MSCHAP")) {
+ config.enterpriseConfig.setPhase2Method(WifiEnterpriseConfig.Phase2.MSCHAP);
+ } else if (phase2Value.equals("MSCHAPV2")) {
+ config.enterpriseConfig.setPhase2Method(WifiEnterpriseConfig.Phase2.MSCHAPV2);
+ } else if (phase2Value.equals("GTC")) {
+ config.enterpriseConfig.setPhase2Method(WifiEnterpriseConfig.Phase2.GTC);
+ }
phase2 = false;
}
if (identity) {
String identityValue = new String(ch, start, length);
- config.identity.setValue(identityValue);
+ config.enterpriseConfig.setIdentity(identityValue);
identity = false;
}
if (anonymousidentity) {
String anonyId = new String(ch, start, length);
- config.anonymous_identity.setValue(anonyId);
+ config.enterpriseConfig.setAnonymousIdentity(anonyId);
anonymousidentity = false;
}
if (cacert) {
String cacertValue = new String(ch, start, length);
- // need to install the credentail to "keystore://"
- config.ca_cert.setValue(KEYSTORE_SPACE);
+ config.enterpriseConfig.setCaCertificateAlias(cacertValue);
cacert = false;
}
if (usercert) {
String usercertValue = new String(ch, start, length);
- config.client_cert.setValue(KEYSTORE_SPACE);
+ config.enterpriseConfig.setClientCertificateAlias(usercertValue);
usercert = false;
}
if (ip) {
diff --git a/core/tests/coretests/src/android/util/PatternsTest.java b/core/tests/coretests/src/android/util/PatternsTest.java
index 9519b9f..ebdbb0e 100644
--- a/core/tests/coretests/src/android/util/PatternsTest.java
+++ b/core/tests/coretests/src/android/util/PatternsTest.java
@@ -156,6 +156,8 @@
"Me: 16505551212 this\n",
"Me: 6505551212 this\n",
"Me: 5551212 this\n",
+ "Me: 2211 this\n",
+ "Me: 112 this\n",
"Me: 1-650-555-1212 this\n",
"Me: (650) 555-1212 this\n",
diff --git a/docs/html/tools/debugging/debugging-tracing.jd b/docs/html/tools/debugging/debugging-tracing.jd
index f0d0c0b..7d750cf 100644
--- a/docs/html/tools/debugging/debugging-tracing.jd
+++ b/docs/html/tools/debugging/debugging-tracing.jd
@@ -18,15 +18,6 @@
</ol>
</li>
- <li>
- <a href="#format">Traceview File Format</a>
- <ol>
- <li><a href="#datafileformat">Data File Format</a></li>
-
- <li><a href="#keyfileformat">Key File Format</a></li>
- </ol>
- </li>
-
<li><a href="#creatingtracefiles">Creating Trace Files</a></li>
<li><a href="#copyingfiles">Copying Trace Files to a Host Machine</a></li>
@@ -95,114 +86,6 @@
height="630" />
<p class="img-caption"><strong>Figure 2.</strong> The Traceview Profile Panel</p>
- <h2 id="format">Traceview File Format</h2>
-
- <p>Tracing creates two distinct pieces of output: a <em>data</em> file, which holds the trace
- data, and a <em>key</em> file, which provides a mapping from binary identifiers to thread and
- method names. The files are concatenated when tracing completes, into a single <em>.trace</em>
- file.</p>
-
- <p class="note"><strong>Note:</strong> The previous version of Traceview did not concatenate
- these files for you. If you have old key and data files that you'd still like to trace, you can
- concatenate them yourself with <code>cat mytrace.key mytrace.data >
- mytrace.trace</code>.</p>
-
- <h3 id="datafileformat">Data File Format</h3>
-
- <p>The data file is binary, structured as follows (all values are stored in little-endian
- order):</p>
- <pre>
-* File format:
-* header
-* record 0
-* record 1
-* ...
-*
-* Header format:
-* u4 magic 0x574f4c53 ('SLOW')
-* u2 version
-* u2 offset to data
-* u8 start date/time in usec
-*
-* Record format:
-* u1 thread ID
-* u4 method ID | method action
-* u4 time delta since start, in usec
-</pre>
-
- <p>The application is expected to parse all of the header fields, then seek to "offset to data"
- from the start of the file. From there it just reads 9-byte records until EOF is reached.</p>
-
- <p><em>u8 start date/time in usec</em> is the output from <code>gettimeofday()</code>. It's mainly there so
- that you can tell if the output was generated yesterday or three months ago.</p>
-
- <p><em>method action</em> sits in the two least-significant bits of the <em>method</em> word. The
- currently defined meanings are:</p>
-
- <ul>
- <li>0 - method entry</li>
-
- <li>1 - method exit</li>
-
- <li>2 - method "exited" when unrolled by exception handling</li>
-
- <li>3 - (reserved)</li>
- </ul>
-
- <p>An unsigned 32-bit integer can hold about 70 minutes of time in microseconds.</p>
-
- <h3 id="keyfileformat">Key File Format</h3>
-
- <p>The key file is a plain text file divided into three sections. Each section starts with a
- keyword that begins with '*'. If you see a '*' at the start of a line, you have found the start
- of a new section.</p>
-
- <p>An example file might look like this:</p>
- <pre>
-*version
-1
-clock=global
-*threads
-1 main
-6 JDWP Handler
-5 Async GC
-4 Reference Handler
-3 Finalizer
-2 Signal Handler
-*methods
-0x080f23f8 java/io/PrintStream write ([BII)V
-0x080f25d4 java/io/PrintStream print (Ljava/lang/String;)V
-0x080f27f4 java/io/PrintStream println (Ljava/lang/String;)V
-0x080da620 java/lang/RuntimeException <init> ()V
-[...]
-0x080f630c android/os/Debug startMethodTracing ()V
-0x080f6350 android/os/Debug startMethodTracing (Ljava/lang/String;Ljava/lang/String;I)V
-*end
-</pre>
-<p>The following list describes the major sections of a key file:</p>
- <dl>
- <dt><em>version section</em></dt>
-
- <dd>The first line is the file version number, currently 1. The second line,
- <code>clock=global</code>, indicates that we use a common clock across all threads. A future
- version may use per-thread CPU time counters that are independent for every thread.</dd>
-
- <dt><em>threads section</em></dt>
-
- <dd>One line per thread. Each line consists of two parts: the thread ID, followed by a tab,
- followed by the thread name. There are few restrictions on what a valid thread name is, so
- include everything to the end of the line.</dd>
-
- <dt><em>methods section</em></dt>
-
- <dd>One line per method entry or exit. A line consists of four pieces, separated by tab marks:
- <em>method-ID</em> [TAB] <em>class-name</em> [TAB] <em>method-name</em> [TAB]
- <em>signature</em> . Only the methods that were actually entered or exited are included in the
- list. Note that all three identifiers are required to uniquely identify a method.</dd>
- </dl>
-
- <p>Neither the threads nor methods sections are sorted.</p>
-
<h2 id="creatingtracefiles">Creating Trace Files</h2>
<p>To use Traceview, you need to generate log files containing the trace information you want to
@@ -269,9 +152,6 @@
<p>When using the Android emulator, you must specify an SD card when you create your AVD because the trace files
are written to the SD card. Your application must have permission to write to the SD card as well.
- <p>The format of the trace files is previously described <a href="#format">in this
- document</a>.</p>
-
<h2 id="copyingfiles">Copying Trace Files to a Host Machine</h2>
<p>After your application has run and the system has created your trace files
@@ -297,7 +177,7 @@
You can use the Proguard <code>mapping.txt</code> file to figure out the original unobfuscated names. For more information
on this file, see the <a href="{@docRoot}tools/help/proguard.html">Proguard</a> documentation.</p>
- <h2 id="dmtracedump">Using dmtracdedump</h2>
+ <h2 id="dmtracedump">Using dmtracedump</h2>
<p><code>dmtracedump</code> is a tool that gives you an alternate way of generating
graphical call-stack diagrams from trace log files. The tool uses the Graphviz Dot utility to
@@ -399,4 +279,4 @@
</ol>
</dd>
- </dl>
\ No newline at end of file
+ </dl>
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 9dd2b0d..4b69317 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -74,6 +74,10 @@
}
}
+ public boolean isUnlocked() {
+ return state() == State.UNLOCKED;
+ }
+
public byte[] get(String key) {
try {
return mBinder.get(key);
diff --git a/keystore/tests/src/android/security/AndroidKeyPairGeneratorTest.java b/keystore/tests/src/android/security/AndroidKeyPairGeneratorTest.java
index d108caaa..cd031b4 100644
--- a/keystore/tests/src/android/security/AndroidKeyPairGeneratorTest.java
+++ b/keystore/tests/src/android/security/AndroidKeyPairGeneratorTest.java
@@ -62,11 +62,10 @@
assertTrue(mAndroidKeyStore.reset());
- assertEquals(android.security.KeyStore.State.UNINITIALIZED, mAndroidKeyStore.state());
+ assertFalse(mAndroidKeyStore.isUnlocked());
assertTrue(mAndroidKeyStore.password("1111"));
-
- assertEquals(android.security.KeyStore.State.UNLOCKED, mAndroidKeyStore.state());
+ assertTrue(mAndroidKeyStore.isUnlocked());
assertEquals(0, mAndroidKeyStore.saw("").length);
diff --git a/keystore/tests/src/android/security/AndroidKeyStoreTest.java b/keystore/tests/src/android/security/AndroidKeyStoreTest.java
index c376f3d..8928e06 100644
--- a/keystore/tests/src/android/security/AndroidKeyStoreTest.java
+++ b/keystore/tests/src/android/security/AndroidKeyStoreTest.java
@@ -467,12 +467,10 @@
mAndroidKeyStore = android.security.KeyStore.getInstance();
assertTrue(mAndroidKeyStore.reset());
-
- assertEquals(android.security.KeyStore.State.UNINITIALIZED, mAndroidKeyStore.state());
+ assertFalse(mAndroidKeyStore.isUnlocked());
assertTrue(mAndroidKeyStore.password("1111"));
-
- assertEquals(android.security.KeyStore.State.UNLOCKED, mAndroidKeyStore.state());
+ assertTrue(mAndroidKeyStore.isUnlocked());
assertEquals(0, mAndroidKeyStore.saw("").length);
diff --git a/libs/androidfw/StreamingZipInflater.cpp b/libs/androidfw/StreamingZipInflater.cpp
index d3fb98d..1dfec23 100644
--- a/libs/androidfw/StreamingZipInflater.cpp
+++ b/libs/androidfw/StreamingZipInflater.cpp
@@ -23,6 +23,23 @@
#include <string.h>
#include <stddef.h>
#include <assert.h>
+#include <unistd.h>
+#include <errno.h>
+
+/*
+ * TEMP_FAILURE_RETRY is defined by some, but not all, versions of
+ * <unistd.h>. (Alas, it is not as standard as we'd hoped!) So, if it's
+ * not already defined, then define it here.
+ */
+#ifndef TEMP_FAILURE_RETRY
+/* Used to retry syscalls that can return EINTR. */
+#define TEMP_FAILURE_RETRY(exp) ({ \
+ typeof (exp) _rc; \
+ do { \
+ _rc = (exp); \
+ } while (_rc == -1 && errno == EINTR); \
+ _rc; })
+#endif
static inline size_t min_of(size_t a, size_t b) { return (a < b) ? a : b; }
@@ -135,7 +152,7 @@
// if we don't have any data to decode, read some in. If we're working
// from mmapped data this won't happen, because the clipping to total size
// will prevent reading off the end of the mapped input chunk.
- if (mInflateState.avail_in == 0) {
+ if ((mInflateState.avail_in == 0) && (mDataMap == NULL)) {
int err = readNextChunk();
if (err < 0) {
ALOGE("Unable to access asset data: %d", err);
@@ -191,11 +208,10 @@
if (mInNextChunkOffset < mInTotalSize) {
size_t toRead = min_of(mInBufSize, mInTotalSize - mInNextChunkOffset);
if (toRead > 0) {
- ssize_t didRead = ::read(mFd, mInBuf, toRead);
+ ssize_t didRead = TEMP_FAILURE_RETRY(::read(mFd, mInBuf, toRead));
//ALOGV("Reading input chunk, size %08x didread %08x", toRead, didRead);
if (didRead < 0) {
- // TODO: error
- ALOGE("Error reading asset data");
+ ALOGE("Error reading asset data: %s", strerror(errno));
return didRead;
} else {
mInNextChunkOffset += didRead;
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index cccaf1c..e5cfdf6 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -3384,7 +3384,7 @@
// Tear down existing lockdown if profile was removed
mLockdownEnabled = LockdownVpnTracker.isEnabled();
if (mLockdownEnabled) {
- if (mKeyStore.state() != KeyStore.State.UNLOCKED) {
+ if (!mKeyStore.isUnlocked()) {
Slog.w(TAG, "KeyStore locked; unable to create LockdownTracker");
return false;
}
diff --git a/services/java/com/android/server/connectivity/Vpn.java b/services/java/com/android/server/connectivity/Vpn.java
index bb19cc7..bb7334a 100644
--- a/services/java/com/android/server/connectivity/Vpn.java
+++ b/services/java/com/android/server/connectivity/Vpn.java
@@ -462,7 +462,7 @@
* secondary thread to perform connection work, returning quickly.
*/
public void startLegacyVpn(VpnProfile profile, KeyStore keyStore, LinkProperties egress) {
- if (keyStore.state() != KeyStore.State.UNLOCKED) {
+ if (!keyStore.isUnlocked()) {
throw new IllegalStateException("KeyStore isn't unlocked");
}
diff --git a/wifi/java/android/net/wifi/WifiConfigStore.java b/wifi/java/android/net/wifi/WifiConfigStore.java
index 84506b6..f119a4b 100644
--- a/wifi/java/android/net/wifi/WifiConfigStore.java
+++ b/wifi/java/android/net/wifi/WifiConfigStore.java
@@ -25,7 +25,6 @@
import android.net.NetworkInfo.DetailedState;
import android.net.ProxyProperties;
import android.net.RouteInfo;
-import android.net.wifi.WifiConfiguration.EnterpriseField;
import android.net.wifi.WifiConfiguration.IpAssignment;
import android.net.wifi.WifiConfiguration.KeyMgmt;
import android.net.wifi.WifiConfiguration.ProxySettings;
@@ -37,6 +36,7 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.UserHandle;
+import android.security.KeyStore;
import android.text.TextUtils;
import android.util.Log;
@@ -144,6 +144,7 @@
private static final String EOS = "eos";
private WifiNative mWifiNative;
+ private final KeyStore mKeyStore = KeyStore.getInstance();
WifiConfigStore(Context c, WifiNative wn) {
mContext = c;
@@ -295,16 +296,7 @@
boolean forgetNetwork(int netId) {
if (mWifiNative.removeNetwork(netId)) {
mWifiNative.saveConfig();
- WifiConfiguration target = null;
- WifiConfiguration config = mConfiguredNetworks.get(netId);
- if (config != null) {
- target = mConfiguredNetworks.remove(netId);
- mNetworkIds.remove(configKey(config));
- }
- if (target != null) {
- writeIpAndProxyConfigurations();
- sendConfiguredNetworksChangedBroadcast(target, WifiManager.CHANGE_REASON_REMOVED);
- }
+ removeConfigAndSendBroadcastIfNeeded(netId);
return true;
} else {
loge("Failed to remove network " + netId);
@@ -342,20 +334,27 @@
*/
boolean removeNetwork(int netId) {
boolean ret = mWifiNative.removeNetwork(netId);
- WifiConfiguration config = null;
if (ret) {
- config = mConfiguredNetworks.get(netId);
- if (config != null) {
- config = mConfiguredNetworks.remove(netId);
- mNetworkIds.remove(configKey(config));
- }
- }
- if (config != null) {
- sendConfiguredNetworksChangedBroadcast(config, WifiManager.CHANGE_REASON_REMOVED);
+ removeConfigAndSendBroadcastIfNeeded(netId);
}
return ret;
}
+ private void removeConfigAndSendBroadcastIfNeeded(int netId) {
+ WifiConfiguration config = mConfiguredNetworks.get(netId);
+ if (config != null) {
+ // Remove any associated keys
+ if (config.enterpriseConfig != null) {
+ config.enterpriseConfig.removeKeys(mKeyStore);
+ }
+ mConfiguredNetworks.remove(netId);
+ mNetworkIds.remove(configKey(config));
+
+ writeIpAndProxyConfigurations();
+ sendConfiguredNetworksChangedBroadcast(config, WifiManager.CHANGE_REASON_REMOVED);
+ }
+ }
+
/**
* Enable a network. Note that there is no saveConfig operation.
* This function is retained for compatibility with the public
@@ -1122,34 +1121,57 @@
break setVariables;
}
- for (WifiConfiguration.EnterpriseField field
- : config.enterpriseFields) {
- String varName = field.varName();
- String value = field.value();
- if (value != null) {
- if (field == config.engine) {
- /*
- * If the field is declared as an integer, it must not
- * be null
- */
- if (value.length() == 0) {
- value = "0";
- }
- } else if (field != config.eap) {
- value = (value.length() == 0) ? "NULL" : convertToQuotedString(value);
+ if (config.enterpriseConfig != null) {
+
+ WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
+
+ if (enterpriseConfig.needsKeyStore()) {
+ /**
+ * Keyguard settings may eventually be controlled by device policy.
+ * We check here if keystore is unlocked before installing
+ * credentials.
+ * TODO: Figure a way to store these credentials for wifi alone
+ * TODO: Do we need a dialog here ?
+ */
+ if (mKeyStore.state() != KeyStore.State.UNLOCKED) {
+ loge(config.SSID + ": key store is locked");
+ break setVariables;
}
- if (!mWifiNative.setNetworkVariable(
- netId,
- varName,
- value)) {
- loge(config.SSID + ": failed to set " + varName +
- ": " + value);
+
+ try {
+ /* config passed may include only fields being updated.
+ * In order to generate the key id, fetch uninitialized
+ * fields from the currently tracked configuration
+ */
+ WifiConfiguration currentConfig = mConfiguredNetworks.get(netId);
+ String keyId = config.getKeyIdForCredentials(currentConfig);
+
+ if (!enterpriseConfig.installKeys(mKeyStore, keyId)) {
+ loge(config.SSID + ": failed to install keys");
+ break setVariables;
+ }
+ } catch (IllegalStateException e) {
+ loge(config.SSID + " invalid config for key installation");
break setVariables;
}
}
+
+ HashMap<String, String> enterpriseFields = enterpriseConfig.getFields();
+ for (String key : enterpriseFields.keySet()) {
+ String value = enterpriseFields.get(key);
+ if (!mWifiNative.setNetworkVariable(
+ netId,
+ key,
+ value)) {
+ enterpriseConfig.removeKeys(mKeyStore);
+ loge(config.SSID + ": failed to set " + key +
+ ": " + value);
+ break setVariables;
+ }
+ }
}
updateFailed = false;
- }
+ } //end of setVariables
if (updateFailed) {
if (newNetwork) {
@@ -1445,78 +1467,31 @@
}
}
- for (WifiConfiguration.EnterpriseField field :
- config.enterpriseFields) {
- value = mWifiNative.getNetworkVariable(netId,
- field.varName());
+ if (config.enterpriseConfig == null) {
+ config.enterpriseConfig = new WifiEnterpriseConfig();
+ }
+ HashMap<String, String> enterpriseFields = config.enterpriseConfig.getFields();
+ for (String key : WifiEnterpriseConfig.getSupplicantKeys()) {
+ value = mWifiNative.getNetworkVariable(netId, key);
if (!TextUtils.isEmpty(value)) {
- if (field != config.eap && field != config.engine) {
- value = removeDoubleQuotes(value);
- }
- field.setValue(value);
+ enterpriseFields.put(key, removeDoubleQuotes(value));
+ } else {
+ enterpriseFields.put(key, WifiEnterpriseConfig.EMPTY_VALUE);
}
}
- migrateOldEapTlsIfNecessary(config, netId);
- }
-
- /**
- * Migration code for old EAP-TLS configurations. This should only be used
- * when restoring an old wpa_supplicant.conf or upgrading from a previous
- * platform version.
- *
- * @param config the configuration to be migrated
- * @param netId the wpa_supplicant's net ID
- * @param value the old private_key value
- */
- private void migrateOldEapTlsIfNecessary(WifiConfiguration config, int netId) {
- String value = mWifiNative.getNetworkVariable(netId,
- WifiConfiguration.OLD_PRIVATE_KEY_NAME);
- /*
- * If the old configuration value is not present, then there is nothing
- * to do.
- */
- if (TextUtils.isEmpty(value)) {
- return;
- } else {
- // Also ignore it if it's empty quotes.
- value = removeDoubleQuotes(value);
- if (TextUtils.isEmpty(value)) {
- return;
- }
+ if (config.enterpriseConfig.migrateOldEapTlsNative(mWifiNative, netId)) {
+ saveConfig();
}
-
- config.engine.setValue(WifiConfiguration.ENGINE_ENABLE);
- config.engine_id.setValue(convertToQuotedString(WifiConfiguration.KEYSTORE_ENGINE_ID));
-
- /*
- * The old key started with the keystore:// URI prefix, but we don't
- * need that anymore. Trim it off if it exists.
- */
- final String keyName;
- if (value.startsWith(WifiConfiguration.KEYSTORE_URI)) {
- keyName = new String(value.substring(WifiConfiguration.KEYSTORE_URI.length()));
- } else {
- keyName = value;
- }
- config.key_id.setValue(convertToQuotedString(keyName));
-
- // Now tell the wpa_supplicant the new configuration values.
- final EnterpriseField needsUpdate[] = { config.engine, config.engine_id, config.key_id };
- for (EnterpriseField field : needsUpdate) {
- mWifiNative.setNetworkVariable(netId, field.varName(), field.value());
- }
-
- // Remove old private_key string so we don't run this again.
- mWifiNative.setNetworkVariable(netId, WifiConfiguration.OLD_PRIVATE_KEY_NAME,
- convertToQuotedString(""));
-
- saveConfig();
}
private String removeDoubleQuotes(String string) {
- if (string.length() <= 2) return "";
- return string.substring(1, string.length() - 1);
+ int length = string.length();
+ if ((length > 1) && (string.charAt(0) == '"')
+ && (string.charAt(length - 1) == '"')) {
+ return string.substring(1, length - 1);
+ }
+ return string;
}
private String convertToQuotedString(String string) {
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index c4fe1b4..bf82792 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -19,49 +19,16 @@
import android.net.LinkProperties;
import android.os.Parcelable;
import android.os.Parcel;
+import android.text.TextUtils;
import java.util.BitSet;
/**
* A class representing a configured Wi-Fi network, including the
- * security configuration. Android will not necessarily support
- * all of these security schemes initially.
+ * security configuration.
*/
public class WifiConfiguration implements Parcelable {
-
- /**
- * In old configurations, the "private_key" field was used. However, newer
- * configurations use the key_id field with the engine_id set to "keystore".
- * If this field is found in the configuration, the migration code is
- * triggered.
- * @hide
- */
- public static final String OLD_PRIVATE_KEY_NAME = "private_key";
-
- /**
- * String representing the keystore OpenSSL ENGINE's ID.
- * @hide
- */
- public static final String KEYSTORE_ENGINE_ID = "keystore";
-
- /**
- * String representing the keystore URI used for wpa_supplicant.
- * @hide
- */
- public static final String KEYSTORE_URI = "keystore://";
-
- /**
- * String to set the engine value to when it should be enabled.
- * @hide
- */
- public static final String ENGINE_ENABLE = "1";
-
- /**
- * String to set the engine value to when it should be disabled.
- * @hide
- */
- public static final String ENGINE_DISABLE = "0";
-
+ private static final String TAG = "WifiConfiguration";
/** {@hide} */
public static final String ssidVarName = "ssid";
/** {@hide} */
@@ -78,56 +45,6 @@
public static final String hiddenSSIDVarName = "scan_ssid";
/** {@hide} */
public static final int INVALID_NETWORK_ID = -1;
-
- /** {@hide} */
- public class EnterpriseField {
- private String varName;
- private String value;
-
- private EnterpriseField(String varName) {
- this.varName = varName;
- this.value = null;
- }
-
- public void setValue(String value) {
- this.value = value;
- }
-
- public String varName() {
- return varName;
- }
-
- public String value() {
- return value;
- }
- }
-
- /** {@hide} */
- public EnterpriseField eap = new EnterpriseField("eap");
- /** {@hide} */
- public EnterpriseField phase2 = new EnterpriseField("phase2");
- /** {@hide} */
- public EnterpriseField identity = new EnterpriseField("identity");
- /** {@hide} */
- public EnterpriseField anonymous_identity = new EnterpriseField("anonymous_identity");
- /** {@hide} */
- public EnterpriseField password = new EnterpriseField("password");
- /** {@hide} */
- public EnterpriseField client_cert = new EnterpriseField("client_cert");
- /** {@hide} */
- public EnterpriseField engine = new EnterpriseField("engine");
- /** {@hide} */
- public EnterpriseField engine_id = new EnterpriseField("engine_id");
- /** {@hide} */
- public EnterpriseField key_id = new EnterpriseField("key_id");
- /** {@hide} */
- public EnterpriseField ca_cert = new EnterpriseField("ca_cert");
-
- /** {@hide} */
- public EnterpriseField[] enterpriseFields = {
- eap, phase2, identity, anonymous_identity, password, client_cert,
- engine, engine_id, key_id, ca_cert };
-
/**
* Recognized key management schemes.
*/
@@ -357,6 +274,12 @@
* Defaults to CCMP TKIP WEP104 WEP40.
*/
public BitSet allowedGroupCiphers;
+ /**
+ * The enterprise configuration details specifying the EAP method,
+ * certificates and other settings associated with the EAP.
+ * @hide
+ */
+ public WifiEnterpriseConfig enterpriseConfig;
/**
* @hide
@@ -412,11 +335,10 @@
allowedPairwiseCiphers = new BitSet();
allowedGroupCiphers = new BitSet();
wepKeys = new String[4];
- for (int i = 0; i < wepKeys.length; i++)
+ for (int i = 0; i < wepKeys.length; i++) {
wepKeys[i] = null;
- for (EnterpriseField field : enterpriseFields) {
- field.setValue(null);
}
+ enterpriseConfig = new WifiEnterpriseConfig();
ipAssignment = IpAssignment.UNASSIGNED;
proxySettings = ProxySettings.UNASSIGNED;
linkProperties = new LinkProperties();
@@ -496,12 +418,9 @@
sbuf.append('*');
}
- for (EnterpriseField field : enterpriseFields) {
- sbuf.append('\n').append(" " + field.varName() + ": ");
- String value = field.value();
- if (value != null) sbuf.append(value);
- }
+ sbuf.append(enterpriseConfig);
sbuf.append('\n');
+
sbuf.append("IP assignment: " + ipAssignment.toString());
sbuf.append("\n");
sbuf.append("Proxy settings: " + proxySettings.toString());
@@ -545,12 +464,54 @@
return SSID;
}
+ /**
+ * Get an identifier for associating credentials with this config
+ * @param current configuration contains values for additional fields
+ * that are not part of this configuration. Used
+ * when a config with some fields is passed by an application.
+ * @throws IllegalStateException if config is invalid for key id generation
+ * @hide
+ */
+ String getKeyIdForCredentials(WifiConfiguration current) {
+ String keyMgmt = null;
+
+ try {
+ // Get current config details for fields that are not initialized
+ if (TextUtils.isEmpty(SSID)) SSID = current.SSID;
+ if (allowedKeyManagement.cardinality() == 0) {
+ allowedKeyManagement = current.allowedKeyManagement;
+ }
+ if (allowedKeyManagement.get(KeyMgmt.WPA_EAP)) {
+ keyMgmt = KeyMgmt.strings[KeyMgmt.WPA_EAP];
+ }
+ if (allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
+ keyMgmt += KeyMgmt.strings[KeyMgmt.IEEE8021X];
+ }
+
+ if (TextUtils.isEmpty(keyMgmt)) {
+ throw new IllegalStateException("Not an EAP network");
+ }
+
+ return trimStringForKeyId(SSID) + "_" + keyMgmt + "_" +
+ trimStringForKeyId(enterpriseConfig.getKeyId(current != null ?
+ current.enterpriseConfig : null));
+ } catch (NullPointerException e) {
+ throw new IllegalStateException("Invalid config details");
+ }
+ }
+
+ private String trimStringForKeyId(String string) {
+ // Remove quotes and spaces
+ return string.replace("\"", "").replace(" ", "");
+ }
+
private static BitSet readBitSet(Parcel src) {
int cardinality = src.readInt();
BitSet set = new BitSet();
- for (int i = 0; i < cardinality; i++)
+ for (int i = 0; i < cardinality; i++) {
set.set(src.readInt());
+ }
return set;
}
@@ -560,12 +521,16 @@
dest.writeInt(set.cardinality());
- while ((nextSetBit = set.nextSetBit(nextSetBit + 1)) != -1)
+ while ((nextSetBit = set.nextSetBit(nextSetBit + 1)) != -1) {
dest.writeInt(nextSetBit);
+ }
}
/** @hide */
public int getAuthType() {
+ if (allowedKeyManagement.cardinality() > 1) {
+ throw new IllegalStateException("More than one auth type set");
+ }
if (allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
return KeyMgmt.WPA_PSK;
} else if (allowedKeyManagement.get(KeyMgmt.WPA2_PSK)) {
@@ -594,8 +559,9 @@
preSharedKey = source.preSharedKey;
wepKeys = new String[4];
- for (int i = 0; i < wepKeys.length; i++)
+ for (int i = 0; i < wepKeys.length; i++) {
wepKeys[i] = source.wepKeys[i];
+ }
wepTxKeyIndex = source.wepTxKeyIndex;
priority = source.priority;
@@ -606,9 +572,8 @@
allowedPairwiseCiphers = (BitSet) source.allowedPairwiseCiphers.clone();
allowedGroupCiphers = (BitSet) source.allowedGroupCiphers.clone();
- for (int i = 0; i < source.enterpriseFields.length; i++) {
- enterpriseFields[i].setValue(source.enterpriseFields[i].value());
- }
+ enterpriseConfig = new WifiEnterpriseConfig(source.enterpriseConfig);
+
ipAssignment = source.ipAssignment;
proxySettings = source.proxySettings;
linkProperties = new LinkProperties(source.linkProperties);
@@ -623,8 +588,9 @@
dest.writeString(SSID);
dest.writeString(BSSID);
dest.writeString(preSharedKey);
- for (String wepKey : wepKeys)
+ for (String wepKey : wepKeys) {
dest.writeString(wepKey);
+ }
dest.writeInt(wepTxKeyIndex);
dest.writeInt(priority);
dest.writeInt(hiddenSSID ? 1 : 0);
@@ -635,9 +601,8 @@
writeBitSet(dest, allowedPairwiseCiphers);
writeBitSet(dest, allowedGroupCiphers);
- for (EnterpriseField field : enterpriseFields) {
- dest.writeString(field.value());
- }
+ dest.writeParcelable(enterpriseConfig, flags);
+
dest.writeString(ipAssignment.name());
dest.writeString(proxySettings.name());
dest.writeParcelable(linkProperties, flags);
@@ -654,8 +619,9 @@
config.SSID = in.readString();
config.BSSID = in.readString();
config.preSharedKey = in.readString();
- for (int i = 0; i < config.wepKeys.length; i++)
+ for (int i = 0; i < config.wepKeys.length; i++) {
config.wepKeys[i] = in.readString();
+ }
config.wepTxKeyIndex = in.readInt();
config.priority = in.readInt();
config.hiddenSSID = in.readInt() != 0;
@@ -665,13 +631,12 @@
config.allowedPairwiseCiphers = readBitSet(in);
config.allowedGroupCiphers = readBitSet(in);
- for (EnterpriseField field : config.enterpriseFields) {
- field.setValue(in.readString());
- }
+ config.enterpriseConfig = in.readParcelable(null);
config.ipAssignment = IpAssignment.valueOf(in.readString());
config.proxySettings = ProxySettings.valueOf(in.readString());
config.linkProperties = in.readParcelable(null);
+
return config;
}
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.aidl b/wifi/java/android/net/wifi/WifiEnterpriseConfig.aidl
new file mode 100644
index 0000000..b0f5f84
--- /dev/null
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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 android.net.wifi;
+
+parcelable WifiEnterpriseConfig;
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
new file mode 100644
index 0000000..7313e7e
--- /dev/null
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -0,0 +1,666 @@
+/*
+ * 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 android.net.wifi;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.security.Credentials;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.org.bouncycastle.asn1.ASN1InputStream;
+import com.android.org.bouncycastle.asn1.ASN1Sequence;
+import com.android.org.bouncycastle.asn1.DEROctetString;
+import com.android.org.bouncycastle.asn1.x509.BasicConstraints;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.KeyFactory;
+import java.security.KeyStore;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.HashMap;
+import java.util.Map;
+
+/** Enterprise configuration details for Wi-Fi @hide */
+public class WifiEnterpriseConfig implements Parcelable {
+ private static final String TAG = "WifiEnterpriseConfig";
+ /**
+ * In old configurations, the "private_key" field was used. However, newer
+ * configurations use the key_id field with the engine_id set to "keystore".
+ * If this field is found in the configuration, the migration code is
+ * triggered.
+ */
+ private static final String OLD_PRIVATE_KEY_NAME = "private_key";
+
+ /**
+ * String representing the keystore OpenSSL ENGINE's ID.
+ */
+ private static final String ENGINE_ID_KEYSTORE = "keystore";
+
+ /**
+ * String representing the keystore URI used for wpa_supplicant.
+ */
+ private static final String KEYSTORE_URI = "keystore://";
+
+ /**
+ * String to set the engine value to when it should be enabled.
+ */
+ private static final String ENGINE_ENABLE = "1";
+
+ /**
+ * String to set the engine value to when it should be disabled.
+ */
+ private static final String ENGINE_DISABLE = "0";
+
+ private static final String CA_CERT_PREFIX = KEYSTORE_URI + Credentials.CA_CERTIFICATE;
+ private static final String CLIENT_CERT_PREFIX = KEYSTORE_URI + Credentials.USER_CERTIFICATE;
+
+ private static final String EAP_KEY = "eap";
+ private static final String PHASE2_KEY = "phase2";
+ private static final String IDENTITY_KEY = "identity";
+ private static final String ANON_IDENTITY_KEY = "anonymous_identity";
+ private static final String PASSWORD_KEY = "password";
+ private static final String CLIENT_CERT_KEY = "client_cert";
+ private static final String CA_CERT_KEY = "ca_cert";
+ private static final String SUBJECT_MATCH_KEY = "subject_match";
+ private static final String ENGINE_KEY = "engine";
+ private static final String ENGINE_ID_KEY = "engine_id";
+ private static final String PRIVATE_KEY_ID_KEY = "key_id";
+
+ private HashMap<String, String> mFields = new HashMap<String, String>();
+ private X509Certificate mCaCert;
+ private PrivateKey mClientPrivateKey;
+ private X509Certificate mClientCertificate;
+
+ /** This represents an empty value of an enterprise field.
+ * NULL is used at wpa_supplicant to indicate an empty value
+ */
+ static final String EMPTY_VALUE = "NULL";
+
+ public WifiEnterpriseConfig() {
+ // Do not set defaults so that the enterprise fields that are not changed
+ // by API are not changed underneath
+ // This is essential because an app may not have all fields like password
+ // available. It allows modification of subset of fields.
+
+ }
+
+ /** Copy constructor */
+ public WifiEnterpriseConfig(WifiEnterpriseConfig source) {
+ for (String key : source.mFields.keySet()) {
+ mFields.put(key, source.mFields.get(key));
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mFields.size());
+ for (Map.Entry<String, String> entry : mFields.entrySet()) {
+ dest.writeString(entry.getKey());
+ dest.writeString(entry.getValue());
+ }
+
+ writeCertificate(dest, mCaCert);
+
+ if (mClientPrivateKey != null) {
+ String algorithm = mClientPrivateKey.getAlgorithm();
+ byte[] userKeyBytes = mClientPrivateKey.getEncoded();
+ dest.writeInt(userKeyBytes.length);
+ dest.writeByteArray(userKeyBytes);
+ dest.writeString(algorithm);
+ } else {
+ dest.writeInt(0);
+ }
+
+ writeCertificate(dest, mClientCertificate);
+ }
+
+ private void writeCertificate(Parcel dest, X509Certificate cert) {
+ if (cert != null) {
+ try {
+ byte[] certBytes = cert.getEncoded();
+ dest.writeInt(certBytes.length);
+ dest.writeByteArray(certBytes);
+ } catch (CertificateEncodingException e) {
+ dest.writeInt(0);
+ }
+ } else {
+ dest.writeInt(0);
+ }
+ }
+
+ public static final Creator<WifiEnterpriseConfig> CREATOR =
+ new Creator<WifiEnterpriseConfig>() {
+ public WifiEnterpriseConfig createFromParcel(Parcel in) {
+ WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+ int count = in.readInt();
+ for (int i = 0; i < count; i++) {
+ String key = in.readString();
+ String value = in.readString();
+ enterpriseConfig.mFields.put(key, value);
+ }
+
+ enterpriseConfig.mCaCert = readCertificate(in);
+
+ PrivateKey userKey = null;
+ int len = in.readInt();
+ if (len > 0) {
+ try {
+ byte[] bytes = new byte[len];
+ in.readByteArray(bytes);
+ String algorithm = in.readString();
+ KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
+ userKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(bytes));
+ } catch (NoSuchAlgorithmException e) {
+ userKey = null;
+ } catch (InvalidKeySpecException e) {
+ userKey = null;
+ }
+ }
+
+ enterpriseConfig.mClientPrivateKey = userKey;
+ enterpriseConfig.mClientCertificate = readCertificate(in);
+ return enterpriseConfig;
+ }
+
+ private X509Certificate readCertificate(Parcel in) {
+ X509Certificate cert = null;
+ int len = in.readInt();
+ if (len > 0) {
+ try {
+ byte[] bytes = new byte[len];
+ in.readByteArray(bytes);
+ CertificateFactory cFactory = CertificateFactory.getInstance("X.509");
+ cert = (X509Certificate) cFactory
+ .generateCertificate(new ByteArrayInputStream(bytes));
+ } catch (CertificateException e) {
+ cert = null;
+ }
+ }
+ return cert;
+ }
+
+ public WifiEnterpriseConfig[] newArray(int size) {
+ return new WifiEnterpriseConfig[size];
+ }
+ };
+
+ public static final class Eap {
+ /* NONE represents an empty enterprise config */
+ public static final int NONE = -1;
+ public static final int PEAP = 0;
+ public static final int TLS = 1;
+ public static final int TTLS = 2;
+ public static final int PWD = 3;
+ /** @hide */
+ public static final String[] strings = { "PEAP", "TLS", "TTLS", "PWD" };
+ }
+
+ public static final class Phase2 {
+ public static final int NONE = 0;
+ public static final int PAP = 1;
+ public static final int MSCHAP = 2;
+ public static final int MSCHAPV2 = 3;
+ public static final int GTC = 4;
+ private static final String PREFIX = "auth=";
+ /** @hide */
+ public static final String[] strings = {EMPTY_VALUE, "PAP", "MSCHAP", "MSCHAPV2", "GTC" };
+ }
+
+ /** Internal use only */
+ HashMap<String, String> getFields() {
+ return mFields;
+ }
+
+ /** Internal use only */
+ static String[] getSupplicantKeys() {
+ return new String[] { EAP_KEY, PHASE2_KEY, IDENTITY_KEY, ANON_IDENTITY_KEY, PASSWORD_KEY,
+ CLIENT_CERT_KEY, CA_CERT_KEY, SUBJECT_MATCH_KEY, ENGINE_KEY, ENGINE_ID_KEY,
+ PRIVATE_KEY_ID_KEY };
+ }
+
+ /**
+ * Set the EAP authentication method.
+ * @param eapMethod is one {@link Eap#PEAP}, {@link Eap#TLS}, {@link Eap#TTLS} or
+ * {@link Eap#PWD}
+ */
+ public void setEapMethod(int eapMethod) {
+ switch (eapMethod) {
+ /** Valid methods */
+ case Eap.PEAP:
+ case Eap.PWD:
+ case Eap.TLS:
+ case Eap.TTLS:
+ mFields.put(EAP_KEY, Eap.strings[eapMethod]);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown EAP method");
+ }
+ }
+
+ /**
+ * Get the eap method.
+ * @return eap method configured
+ */
+ public int getEapMethod() {
+ String eapMethod = mFields.get(EAP_KEY);
+ return getStringIndex(Eap.strings, eapMethod, Eap.NONE);
+ }
+
+ /**
+ * Set Phase 2 authentication method. Sets the inner authentication method to be used in
+ * phase 2 after setting up a secure channel
+ * @param phase2Method is the inner authentication method and can be one of {@link Phase2#NONE},
+ * {@link Phase2#PAP}, {@link Phase2#MSCHAP}, {@link Phase2#MSCHAPV2},
+ * {@link Phase2#GTC}
+ *
+ */
+ public void setPhase2Method(int phase2Method) {
+ switch (phase2Method) {
+ case Phase2.NONE:
+ mFields.put(PHASE2_KEY, EMPTY_VALUE);
+ break;
+ /** Valid methods */
+ case Phase2.PAP:
+ case Phase2.MSCHAP:
+ case Phase2.MSCHAPV2:
+ case Phase2.GTC:
+ mFields.put(PHASE2_KEY, convertToQuotedString(
+ Phase2.PREFIX + Phase2.strings[phase2Method]));
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown Phase 2 method");
+ }
+ }
+
+ /**
+ * Get the phase 2 authentication method.
+ * @return a phase 2 method defined at {@link Phase2}
+ * */
+ public int getPhase2Method() {
+ String phase2Method = removeDoubleQuotes(mFields.get(PHASE2_KEY));
+ // Remove auth= prefix
+ if (phase2Method.startsWith(Phase2.PREFIX)) {
+ phase2Method = phase2Method.substring(Phase2.PREFIX.length());
+ }
+ return getStringIndex(Phase2.strings, phase2Method, Phase2.NONE);
+ }
+
+ /**
+ * Set the identity
+ * @param identity
+ */
+ public void setIdentity(String identity) {
+ setFieldValue(IDENTITY_KEY, identity, "");
+ }
+
+ /**
+ * Get the identity
+ * @return the identity
+ */
+ public String getIdentity() {
+ return getFieldValue(IDENTITY_KEY, "");
+ }
+
+ /**
+ * Set anonymous identity. This is used as the unencrypted identity with
+ * certain EAP types
+ * @param anonymousIdentity the anonymous identity
+ */
+ public void setAnonymousIdentity(String anonymousIdentity) {
+ setFieldValue(ANON_IDENTITY_KEY, anonymousIdentity, "");
+ }
+
+ /** Get the anonymous identity
+ * @return anonymous identity
+ */
+ public String getAnonymousIdentity() {
+ return getFieldValue(ANON_IDENTITY_KEY, "");
+ }
+
+ /**
+ * Set the password.
+ * @param password the password
+ */
+ public void setPassword(String password) {
+ setFieldValue(PASSWORD_KEY, password, "");
+ }
+
+ /**
+ * Set CA certificate alias.
+ *
+ * <p> See the {@link android.security.KeyChain} for details on installing or choosing
+ * a certificate
+ * </p>
+ * @param alias identifies the certificate
+ * @hide
+ */
+ public void setCaCertificateAlias(String alias) {
+ setFieldValue(CA_CERT_KEY, alias, CA_CERT_PREFIX);
+ }
+
+ /**
+ * Get CA certificate alias
+ * @return alias to the CA certificate
+ * @hide
+ */
+ public String getCaCertificateAlias() {
+ return getFieldValue(CA_CERT_KEY, CA_CERT_PREFIX);
+ }
+
+ /**
+ * Specify a X.509 certificate that identifies the server.
+ *
+ * <p>A default name is automatically assigned to the certificate and used
+ * with this configuration.
+ * @param cert X.509 CA certificate
+ * @throws IllegalArgumentException if not a CA certificate
+ */
+ public void setCaCertificate(X509Certificate cert) {
+ if (cert.getBasicConstraints() >= 0) {
+ mCaCert = cert;
+ } else {
+ throw new IllegalArgumentException("Not a CA certificate");
+ }
+ }
+
+ /**
+ * Set Client certificate alias.
+ *
+ * <p> See the {@link android.security.KeyChain} for details on installing or choosing
+ * a certificate
+ * </p>
+ * @param alias identifies the certificate
+ * @hide
+ */
+ public void setClientCertificateAlias(String alias) {
+ setFieldValue(CLIENT_CERT_KEY, alias, CLIENT_CERT_PREFIX);
+ setFieldValue(PRIVATE_KEY_ID_KEY, alias, Credentials.USER_PRIVATE_KEY);
+ // Also, set engine parameters
+ if (TextUtils.isEmpty(alias)) {
+ mFields.put(ENGINE_KEY, ENGINE_DISABLE);
+ mFields.put(ENGINE_ID_KEY, EMPTY_VALUE);
+ } else {
+ mFields.put(ENGINE_KEY, ENGINE_ENABLE);
+ mFields.put(ENGINE_ID_KEY, convertToQuotedString(ENGINE_ID_KEYSTORE));
+ }
+ }
+
+ /**
+ * Get client certificate alias
+ * @return alias to the client certificate
+ * @hide
+ */
+ public String getClientCertificateAlias() {
+ return getFieldValue(CLIENT_CERT_KEY, CLIENT_CERT_PREFIX);
+ }
+
+ /**
+ * Specify a private key and client certificate for client authorization.
+ *
+ * <p>A default name is automatically assigned to the key entry and used
+ * with this configuration.
+ * @param privateKey
+ * @param clientCertificate
+ */
+ public void setClientKeyEntry(PrivateKey privateKey, X509Certificate clientCertificate) {
+ if (clientCertificate != null) {
+ if (clientCertificate.getBasicConstraints() != -1) {
+ throw new IllegalArgumentException("Cannot be a CA certificate");
+ }
+ if (privateKey == null) {
+ throw new IllegalArgumentException("Client cert without a private key");
+ }
+ if (privateKey.getEncoded() == null) {
+ throw new IllegalArgumentException("Private key cannot be encoded");
+ }
+ }
+
+ mClientPrivateKey = privateKey;
+ mClientCertificate = clientCertificate;
+ }
+
+ boolean needsKeyStore() {
+ // Has no keys to be installed
+ if (mClientCertificate == null && mCaCert == null) return false;
+ return true;
+ }
+
+ boolean installKeys(android.security.KeyStore keyStore, String name) {
+ boolean ret = true;
+ String privKeyName = Credentials.USER_PRIVATE_KEY + name;
+ String userCertName = Credentials.USER_CERTIFICATE + name;
+ String caCertName = Credentials.CA_CERTIFICATE + name;
+ if (mClientCertificate != null) {
+ byte[] privKeyData = mClientPrivateKey.getEncoded();
+ ret = keyStore.importKey(privKeyName, privKeyData);
+ if (ret == false) {
+ return ret;
+ }
+
+ ret = putCertInKeyStore(keyStore, userCertName, mClientCertificate);
+ if (ret == false) {
+ // Remove private key installed
+ keyStore.delKey(privKeyName);
+ return ret;
+ }
+ }
+
+ if (mCaCert != null) {
+ ret = putCertInKeyStore(keyStore, caCertName, mCaCert);
+ if (ret == false) {
+ if (mClientCertificate != null) {
+ // Remove client key+cert
+ keyStore.delKey(privKeyName);
+ keyStore.delete(userCertName);
+ }
+ return ret;
+ }
+ }
+
+ // Set alias names
+ if (mClientCertificate != null) {
+ setClientCertificateAlias(name);
+ mClientPrivateKey = null;
+ mClientCertificate = null;
+ }
+
+ if (mCaCert != null) {
+ setCaCertificateAlias(name);
+ mCaCert = null;
+ }
+
+ return ret;
+ }
+
+ private boolean putCertInKeyStore(android.security.KeyStore keyStore, String name,
+ Certificate cert) {
+ try {
+ byte[] certData = Credentials.convertToPem(cert);
+ return keyStore.put(name, certData);
+ } catch (IOException e1) {
+ return false;
+ } catch (CertificateException e2) {
+ return false;
+ }
+ }
+
+ void removeKeys(android.security.KeyStore keyStore) {
+ String client = getFieldValue(CLIENT_CERT_KEY, CLIENT_CERT_PREFIX);
+ // a valid client certificate is configured
+ if (!TextUtils.isEmpty(client)) {
+ keyStore.delKey(Credentials.USER_PRIVATE_KEY + client);
+ keyStore.delete(Credentials.USER_CERTIFICATE + client);
+ }
+
+ String ca = getFieldValue(CA_CERT_KEY, CA_CERT_PREFIX);
+ // a valid ca certificate is configured
+ if (!TextUtils.isEmpty(ca)) {
+ keyStore.delete(Credentials.CA_CERTIFICATE + ca);
+ }
+ }
+
+ /**
+ * Set subject match. This is the substring to be matched against the subject of the
+ * authentication server certificate.
+ * @param subjectMatch substring to be matched
+ */
+ public void setSubjectMatch(String subjectMatch) {
+ setFieldValue(SUBJECT_MATCH_KEY, subjectMatch, "");
+ }
+
+ /**
+ * Get subject match
+ * @return the subject match string
+ */
+ public String getSubjectMatch() {
+ return getFieldValue(SUBJECT_MATCH_KEY, "");
+ }
+
+ /** See {@link WifiConfiguration#getKeyIdForCredentials} @hide */
+ String getKeyId(WifiEnterpriseConfig current) {
+ String eap = mFields.get(EAP_KEY);
+ String phase2 = mFields.get(PHASE2_KEY);
+
+ // If either eap or phase2 are not initialized, use current config details
+ if (TextUtils.isEmpty((eap))) {
+ eap = current.mFields.get(EAP_KEY);
+ }
+ if (TextUtils.isEmpty(phase2)) {
+ phase2 = current.mFields.get(PHASE2_KEY);
+ }
+ return eap + "_" + phase2;
+ }
+
+ /** Migrates the old style TLS config to the new config style. This should only be used
+ * when restoring an old wpa_supplicant.conf or upgrading from a previous
+ * platform version.
+ * @return true if the config was updated
+ * @hide
+ */
+ boolean migrateOldEapTlsNative(WifiNative wifiNative, int netId) {
+ String oldPrivateKey = wifiNative.getNetworkVariable(netId, OLD_PRIVATE_KEY_NAME);
+ /*
+ * If the old configuration value is not present, then there is nothing
+ * to do.
+ */
+ if (TextUtils.isEmpty(oldPrivateKey)) {
+ return false;
+ } else {
+ // Also ignore it if it's empty quotes.
+ oldPrivateKey = removeDoubleQuotes(oldPrivateKey);
+ if (TextUtils.isEmpty(oldPrivateKey)) {
+ return false;
+ }
+ }
+
+ mFields.put(ENGINE_KEY, ENGINE_ENABLE);
+ mFields.put(ENGINE_ID_KEY, convertToQuotedString(ENGINE_ID_KEYSTORE));
+
+ /*
+ * The old key started with the keystore:// URI prefix, but we don't
+ * need that anymore. Trim it off if it exists.
+ */
+ final String keyName;
+ if (oldPrivateKey.startsWith(KEYSTORE_URI)) {
+ keyName = new String(oldPrivateKey.substring(KEYSTORE_URI.length()));
+ } else {
+ keyName = oldPrivateKey;
+ }
+ mFields.put(PRIVATE_KEY_ID_KEY, convertToQuotedString(keyName));
+
+ wifiNative.setNetworkVariable(netId, ENGINE_KEY, mFields.get(ENGINE_KEY));
+ wifiNative.setNetworkVariable(netId, ENGINE_ID_KEY, mFields.get(ENGINE_ID_KEY));
+ wifiNative.setNetworkVariable(netId, PRIVATE_KEY_ID_KEY, mFields.get(PRIVATE_KEY_ID_KEY));
+ // Remove old private_key string so we don't run this again.
+ wifiNative.setNetworkVariable(netId, OLD_PRIVATE_KEY_NAME, EMPTY_VALUE);
+ return true;
+ }
+
+ private String removeDoubleQuotes(String string) {
+ int length = string.length();
+ if ((length > 1) && (string.charAt(0) == '"')
+ && (string.charAt(length - 1) == '"')) {
+ return string.substring(1, length - 1);
+ }
+ return string;
+ }
+
+ private String convertToQuotedString(String string) {
+ return "\"" + string + "\"";
+ }
+
+ /** Returns the index at which the toBeFound string is found in the array.
+ * @param arr array of strings
+ * @param toBeFound string to be found
+ * @param defaultIndex default index to be returned when string is not found
+ * @return the index into array
+ */
+ private int getStringIndex(String arr[], String toBeFound, int defaultIndex) {
+ if (TextUtils.isEmpty(toBeFound)) return defaultIndex;
+ for (int i = 0; i < arr.length; i++) {
+ if (toBeFound.equals(arr[i])) return i;
+ }
+ return defaultIndex;
+ }
+
+ /** Returns the field value for the key.
+ * @param key into the hash
+ * @param prefix is the prefix that the value may have
+ * @return value
+ */
+ private String getFieldValue(String key, String prefix) {
+ String value = mFields.get(key);
+ // Uninitialized or known to be empty after reading from supplicant
+ if (TextUtils.isEmpty(value) || EMPTY_VALUE.equals(value)) return "";
+ return removeDoubleQuotes(value).substring(prefix.length());
+ }
+
+ /** Set a value with an optional prefix at key
+ * @param key into the hash
+ * @param value to be set
+ * @param prefix an optional value to be prefixed to actual value
+ */
+ private void setFieldValue(String key, String value, String prefix) {
+ if (TextUtils.isEmpty(value)) {
+ mFields.put(key, EMPTY_VALUE);
+ } else {
+ mFields.put(key, convertToQuotedString(prefix + value));
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ for (String key : mFields.keySet()) {
+ sb.append(key).append(" ").append(mFields.get(key)).append("\n");
+ }
+ return sb.toString();
+ }
+}