Merge "Fix bug 5258435 - ActionBar.setBackgroundDrawable"
diff --git a/CleanSpec.mk b/CleanSpec.mk
index e6c4183..c1799a1 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -108,6 +108,7 @@
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/media/java/android/media/IAudioService.P)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/media/audio/)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/nfc/)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/wifi/java)
# ************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
# ************************************************
diff --git a/api/current.txt b/api/current.txt
index d7ed27c..2930f46 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6216,6 +6216,7 @@
field public static final java.lang.String FEATURE_USB_ACCESSORY = "android.hardware.usb.accessory";
field public static final java.lang.String FEATURE_USB_HOST = "android.hardware.usb.host";
field public static final java.lang.String FEATURE_WIFI = "android.hardware.wifi";
+ field public static final java.lang.String FEATURE_WIFI_DIRECT = "android.hardware.wifi.direct";
field public static final int GET_ACTIVITIES = 1; // 0x1
field public static final int GET_CONFIGURATIONS = 16384; // 0x4000
field public static final int GET_DISABLED_COMPONENTS = 512; // 0x200
@@ -9328,6 +9329,7 @@
method public boolean isAutoExposureLockSupported();
method public boolean isAutoWhiteBalanceLockSupported();
method public boolean isSmoothZoomSupported();
+ method public boolean isVideoSnapshotSupported();
method public boolean isZoomSupported();
method public void remove(java.lang.String);
method public void removeGpsData();
diff --git a/core/java/android/animation/LayoutTransition.java b/core/java/android/animation/LayoutTransition.java
index 9e2b833..355b1fc 100644
--- a/core/java/android/animation/LayoutTransition.java
+++ b/core/java/android/animation/LayoutTransition.java
@@ -793,7 +793,9 @@
* @hide
*/
public void startChangingAnimations() {
- for (Animator anim : currentChangingAnimations.values()) {
+ LinkedHashMap<View, Animator> currentAnimCopy =
+ (LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
+ for (Animator anim : currentAnimCopy.values()) {
if (anim instanceof ObjectAnimator) {
((ObjectAnimator) anim).setCurrentPlayTime(0);
}
@@ -802,6 +804,23 @@
}
/**
+ * Ends the animations that are set up for a CHANGING transition. This is a variant of
+ * startChangingAnimations() which is called when the window the transition is playing in
+ * is not visible. We need to make sure the animations put their targets in their end states
+ * and that the transition finishes to remove any mid-process state (such as isRunning()).
+ *
+ * @hide
+ */
+ public void endChangingAnimations() {
+ LinkedHashMap<View, Animator> currentAnimCopy =
+ (LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
+ for (Animator anim : currentAnimCopy.values()) {
+ anim.start();
+ anim.end();
+ }
+ }
+
+ /**
* Returns true if animations are running which animate layout-related properties. This
* essentially means that either CHANGE_APPEARING or CHANGE_DISAPPEARING animations
* are running, since these animations operate on layout-related properties.
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 5c641f1..b4e3988 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1021,6 +1021,13 @@
public static final String FEATURE_WIFI = "android.hardware.wifi";
/**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports Wi-Fi Direct networking.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_WIFI_DIRECT = "android.hardware.wifi.direct";
+
+ /**
* Action to external storage service to clean out removed apps.
* @hide
*/
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 63f2244..58f7869 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -3233,7 +3233,6 @@
* captured pictures.
*
* @return true if video snapshot is supported.
- * @hide
*/
public boolean isVideoSnapshotSupported() {
String str = get(KEY_VIDEO_SNAPSHOT_SUPPORTED);
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 370e22a..7d3cd92 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -618,7 +618,7 @@
mTheme = Resources.selectSystemTheme(mTheme,
getApplicationInfo().targetSdkVersion,
android.R.style.Theme_InputMethod,
- android.R.style.Theme_Holo,
+ android.R.style.Theme_Holo_InputMethod,
android.R.style.Theme_DeviceDefault_InputMethod);
super.setTheme(mTheme);
super.onCreate();
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2e2b3d6..b3dd071 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1575,13 +1575,13 @@
boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw() ||
viewVisibility != View.VISIBLE;
- if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
- for (int i = 0; i < mPendingTransitions.size(); ++i) {
- mPendingTransitions.get(i).startChangingAnimations();
- }
- mPendingTransitions.clear();
- }
if (!cancelDraw && !newSurface) {
+ if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
+ for (int i = 0; i < mPendingTransitions.size(); ++i) {
+ mPendingTransitions.get(i).startChangingAnimations();
+ }
+ mPendingTransitions.clear();
+ }
mFullRedrawNeeded = false;
final long drawStartTime;
@@ -1619,7 +1619,13 @@
}
}
} else {
-
+ // End any pending transitions on this non-visible window
+ if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
+ for (int i = 0; i < mPendingTransitions.size(); ++i) {
+ mPendingTransitions.get(i).endChangingAnimations();
+ }
+ mPendingTransitions.clear();
+ }
// We were supposed to report when we are done drawing. Since we canceled the
// draw, remember it here.
if ((relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0) {
diff --git a/core/java/android/view/animation/AnimationSet.java b/core/java/android/view/animation/AnimationSet.java
index 4f2542b..58373bc 100644
--- a/core/java/android/view/animation/AnimationSet.java
+++ b/core/java/android/view/animation/AnimationSet.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.content.res.TypedArray;
+import android.os.Build;
import android.util.AttributeSet;
import android.graphics.RectF;
@@ -31,6 +32,22 @@
* If AnimationSet sets any properties that its children also set
* (for example, duration or fillBefore), the values of AnimationSet
* override the child values.
+ *
+ * <p>The way that AnimationSet inherits behavior from Animation is important to
+ * understand. Some of the Animation attributes applied to AnimationSet affect the
+ * AnimationSet itself, some are pushed down to the children, and some are ignored,
+ * as follows:
+ * <ul>
+ * <li>duration, repeatMode, fillBefore, fillAfter: These properties, when set
+ * on an AnimationSet object, will be pushed down to all child animations.</li>
+ * <li>repeatCount, fillEnabled: These properties are ignored for AnimationSet.</li>
+ * <li>startOffset, shareInterpolator: These properties apply to the AnimationSet itself.</li>
+ * </ul>
+ * Starting with {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH},
+ * the behavior of these properties is the same in XML resources and at runtime (prior to that
+ * release, the values set in XML were ignored for AnimationSet). That is, calling
+ * <code>setDuration(500)</code> on an AnimationSet has the same effect as declaring
+ * <code>android:duration="500"</code> in an XML resource for an AnimationSet object.</p>
*/
public class AnimationSet extends Animation {
private static final int PROPERTY_FILL_AFTER_MASK = 0x1;
@@ -69,7 +86,26 @@
setFlag(PROPERTY_SHARE_INTERPOLATOR_MASK,
a.getBoolean(com.android.internal.R.styleable.AnimationSet_shareInterpolator, true));
init();
-
+
+ if (context.getApplicationInfo().targetSdkVersion >=
+ Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ if (a.hasValue(com.android.internal.R.styleable.Animation_duration)) {
+ mFlags |= PROPERTY_DURATION_MASK;
+ }
+ if (a.hasValue(com.android.internal.R.styleable.Animation_fillBefore)) {
+ mFlags |= PROPERTY_FILL_BEFORE_MASK;
+ }
+ if (a.hasValue(com.android.internal.R.styleable.Animation_fillAfter)) {
+ mFlags |= PROPERTY_FILL_AFTER_MASK;
+ }
+ if (a.hasValue(com.android.internal.R.styleable.Animation_repeatMode)) {
+ mFlags |= PROPERTY_REPEAT_MODE_MASK;
+ }
+ if (a.hasValue(com.android.internal.R.styleable.Animation_startOffset)) {
+ mFlags |= PROPERTY_START_OFFSET_MASK;
+ }
+ }
+
a.recycle();
}
@@ -112,7 +148,6 @@
private void init() {
mStartTime = 0;
- mDuration = 0;
}
@Override
@@ -171,6 +206,7 @@
public void setDuration(long durationMillis) {
mFlags |= PROPERTY_DURATION_MASK;
super.setDuration(durationMillis);
+ mLastEnd = mStartOffset + mDuration;
}
/**
@@ -192,12 +228,16 @@
mFlags |= PROPERTY_CHANGE_BOUNDS_MASK;
}
- if (mAnimations.size() == 1) {
- mDuration = a.getStartOffset() + a.getDuration();
+ if ((mFlags & PROPERTY_DURATION_MASK) == PROPERTY_DURATION_MASK) {
mLastEnd = mStartOffset + mDuration;
} else {
- mLastEnd = Math.max(mLastEnd, a.getStartOffset() + a.getDuration());
- mDuration = mLastEnd - mStartOffset;
+ if (mAnimations.size() == 1) {
+ mDuration = a.getStartOffset() + a.getDuration();
+ mLastEnd = mStartOffset + mDuration;
+ } else {
+ mLastEnd = Math.max(mLastEnd, a.getStartOffset() + a.getDuration());
+ mDuration = mLastEnd - mStartOffset;
+ }
}
mDirty = true;
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 5bb0ef2..ef4bcc1 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -20,6 +20,7 @@
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.ClipboardManager;
+import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
@@ -9404,4 +9405,8 @@
native boolean nativeSetProperty(String key, String value);
native String nativeGetProperty(String key);
private native void nativeGetTextSelectionRegion(Region region);
+ /**
+ * See {@link ComponentCallbacks2} for the trim levels and descriptions
+ */
+ private static native void nativeOnTrimMemory(int level);
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 7f410aa..d8aaa19 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -9555,6 +9555,8 @@
public void dismiss() {
super.dismiss();
+ TextView.this.getPositionListener().removeSubscriber(SuggestionsPopupWindow.this);
+
if ((mText instanceof Editable) && mSuggestionRangeSpan != null) {
((Editable) mText).removeSpan(mSuggestionRangeSpan);
}
diff --git a/core/java/com/android/internal/backup/BackupConstants.java b/core/java/com/android/internal/backup/BackupConstants.java
index 3ee11bd..906b5d5 100644
--- a/core/java/com/android/internal/backup/BackupConstants.java
+++ b/core/java/com/android/internal/backup/BackupConstants.java
@@ -23,4 +23,5 @@
public static final int TRANSPORT_OK = 0;
public static final int TRANSPORT_ERROR = 1;
public static final int TRANSPORT_NOT_INITIALIZED = 2;
+ public static final int AGENT_ERROR = 3;
}
diff --git a/core/res/res/drawable-hdpi/list_longpressed_holo.9.png b/core/res/res/drawable-hdpi/list_longpressed_holo.9.png
index d06549c..4ea7afa 100644
--- a/core/res/res/drawable-hdpi/list_longpressed_holo.9.png
+++ b/core/res/res/drawable-hdpi/list_longpressed_holo.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/list_selected_holo_dark.9.png b/core/res/res/drawable-hdpi/list_selected_holo_dark.9.png
index dae40ca..e20b02d 100644
--- a/core/res/res/drawable-hdpi/list_selected_holo_dark.9.png
+++ b/core/res/res/drawable-hdpi/list_selected_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/list_selected_holo_light.9.png b/core/res/res/drawable-hdpi/list_selected_holo_light.9.png
index dae40ca..e20b02d 100644
--- a/core/res/res/drawable-hdpi/list_selected_holo_light.9.png
+++ b/core/res/res/drawable-hdpi/list_selected_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_longpressed_holo.9.png b/core/res/res/drawable-mdpi/list_longpressed_holo.9.png
index 2b8a0b3..3bf8e03 100644
--- a/core/res/res/drawable-mdpi/list_longpressed_holo.9.png
+++ b/core/res/res/drawable-mdpi/list_longpressed_holo.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_selected_holo_dark.9.png b/core/res/res/drawable-mdpi/list_selected_holo_dark.9.png
index 4cbcee9..13cb131 100644
--- a/core/res/res/drawable-mdpi/list_selected_holo_dark.9.png
+++ b/core/res/res/drawable-mdpi/list_selected_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_selected_holo_light.9.png b/core/res/res/drawable-mdpi/list_selected_holo_light.9.png
index 4cbcee9..13cb131 100644
--- a/core/res/res/drawable-mdpi/list_selected_holo_light.9.png
+++ b/core/res/res/drawable-mdpi/list_selected_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/list_longpressed_holo.9.png b/core/res/res/drawable-xhdpi/list_longpressed_holo.9.png
index e303022..eda10e6 100644
--- a/core/res/res/drawable-xhdpi/list_longpressed_holo.9.png
+++ b/core/res/res/drawable-xhdpi/list_longpressed_holo.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/list_selected_holo_dark.9.png b/core/res/res/drawable-xhdpi/list_selected_holo_dark.9.png
index 4375032..ee5eb6f 100644
--- a/core/res/res/drawable-xhdpi/list_selected_holo_dark.9.png
+++ b/core/res/res/drawable-xhdpi/list_selected_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/list_selected_holo_light.9.png b/core/res/res/drawable-xhdpi/list_selected_holo_light.9.png
index 4375032..ee5eb6f 100644
--- a/core/res/res/drawable-xhdpi/list_selected_holo_light.9.png
+++ b/core/res/res/drawable-xhdpi/list_selected_holo_light.9.png
Binary files differ
diff --git a/data/etc/android.hardware.wifi.direct.xml b/data/etc/android.hardware.wifi.direct.xml
new file mode 100644
index 0000000..78cb474
--- /dev/null
+++ b/data/etc/android.hardware.wifi.direct.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- This is the standard feature indicating that the device includes WiFi Direct. -->
+<permissions>
+ <feature name="android.hardware.wifi.direct" />
+</permissions>
diff --git a/media/libmedia/IMediaPlayer.cpp b/media/libmedia/IMediaPlayer.cpp
index bd89ad8..50a41ca 100644
--- a/media/libmedia/IMediaPlayer.cpp
+++ b/media/libmedia/IMediaPlayer.cpp
@@ -108,6 +108,7 @@
Parcel data, reply;
data.writeInterfaceToken(IMediaPlayer::getInterfaceDescriptor());
data.writeStrongBinder(source->asBinder());
+ remote()->transact(SET_DATA_SOURCE_STREAM, data, &reply);
return reply.readInt32();
}
diff --git a/media/libstagefright/matroska/MatroskaExtractor.cpp b/media/libstagefright/matroska/MatroskaExtractor.cpp
index 3ef7b71..ffa3356 100644
--- a/media/libstagefright/matroska/MatroskaExtractor.cpp
+++ b/media/libstagefright/matroska/MatroskaExtractor.cpp
@@ -93,7 +93,7 @@
void advance();
void reset();
- void seek(int64_t seekTimeUs);
+ void seek(int64_t seekTimeUs, bool seekToKeyFrame);
const mkvparser::Block *block() const;
int64_t blockTimeUs() const;
@@ -137,6 +137,7 @@
sp<MatroskaExtractor> mExtractor;
size_t mTrackIndex;
Type mType;
+ bool mIsAudio;
BlockIterator mBlockIter;
size_t mNALSizeLen; // for type AVC
@@ -156,6 +157,7 @@
: mExtractor(extractor),
mTrackIndex(index),
mType(OTHER),
+ mIsAudio(false),
mBlockIter(mExtractor.get(),
mExtractor->mTracks.itemAt(index).mTrackNum),
mNALSizeLen(0) {
@@ -164,6 +166,8 @@
const char *mime;
CHECK(meta->findCString(kKeyMIMEType, &mime));
+ mIsAudio = !strncasecmp("audio/", mime, 6);
+
if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC)) {
mType = AVC;
@@ -299,7 +303,7 @@
} while (!eos() && block()->GetTrackNumber() != mTrackNum);
}
-void BlockIterator::seek(int64_t seekTimeUs) {
+void BlockIterator::seek(int64_t seekTimeUs, bool seekToKeyFrame) {
Mutex::Autolock autoLock(mExtractor->mLock);
mCluster = mExtractor->mSegment->FindCluster(seekTimeUs * 1000ll);
@@ -311,8 +315,10 @@
}
while (!eos() && block()->GetTrackNumber() != mTrackNum);
- while (!eos() && !mBlockEntry->GetBlock()->IsKey()) {
- advance_l();
+ if (seekToKeyFrame) {
+ while (!eos() && !mBlockEntry->GetBlock()->IsKey()) {
+ advance_l();
+ }
}
}
@@ -396,7 +402,11 @@
if (options && options->getSeekTo(&seekTimeUs, &mode)
&& !mExtractor->isLiveStreaming()) {
clearPendingFrames();
- mBlockIter.seek(seekTimeUs);
+
+ // Apparently keyframe indication in audio tracks is unreliable,
+ // fortunately in all our currently supported audio encodings every
+ // frame is effectively a keyframe.
+ mBlockIter.seek(seekTimeUs, !mIsAudio);
}
again:
@@ -537,6 +547,13 @@
return;
}
+#if 0
+ const mkvparser::SegmentInfo *info = mSegment->GetInfo();
+ LOGI("muxing app: %s, writing app: %s",
+ info->GetMuxingAppAsUTF8(),
+ info->GetWritingAppAsUTF8());
+#endif
+
addTracks();
}
diff --git a/media/libstagefright/tests/SurfaceMediaSource_test.cpp b/media/libstagefright/tests/SurfaceMediaSource_test.cpp
index d663602..d7bb703 100644
--- a/media/libstagefright/tests/SurfaceMediaSource_test.cpp
+++ b/media/libstagefright/tests/SurfaceMediaSource_test.cpp
@@ -156,8 +156,6 @@
eglDestroySurface(mEglDisplay, mEglSurface);
}
if (mEglDisplay != EGL_NO_DISPLAY) {
- eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
- EGL_NO_CONTEXT);
eglTerminate(mEglDisplay);
}
ASSERT_EQ(EGL_SUCCESS, eglGetError());
@@ -461,6 +459,7 @@
// The following call dequeues and queues the buffer
eglSwapBuffers(mEglDisplay, mEglSurface);
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
glDisable(GL_SCISSOR_TEST);
}
@@ -796,7 +795,12 @@
LOGV("framesCount = %d", nFramesCount);
}
- ASSERT_EQ(NO_ERROR, native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_EGL));
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ EGL_NO_CONTEXT));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ eglDestroySurface(mEglDisplay, mEglSurface);
+ mEglSurface = EGL_NO_SURFACE;
+
writer.stop();
}
// Test to examine whether we can render GL buffers in to the surface
@@ -875,7 +879,12 @@
LOGV("framesCount = %d", nFramesCount);
}
- ASSERT_EQ(NO_ERROR, native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_EGL));
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ EGL_NO_CONTEXT));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ eglDestroySurface(mEglDisplay, mEglSurface);
+ mEglSurface = EGL_NO_SURFACE;
+
LOGV("Stopping MediaRecorder...");
CHECK_EQ(OK, mr->stop());
mr.clear();
@@ -913,7 +922,12 @@
LOGV("framesCount = %d", nFramesCount);
}
- ASSERT_EQ(NO_ERROR, native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_EGL));
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ EGL_NO_CONTEXT));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ eglDestroySurface(mEglDisplay, mEglSurface);
+ mEglSurface = EGL_NO_SURFACE;
+
LOGV("Stopping MediaRecorder...");
CHECK_EQ(OK, mr->stop());
mr.clear();
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index 997318a..be2ef82 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -163,6 +163,10 @@
private static final int MSG_FULL_CONFIRMATION_TIMEOUT = 9;
private static final int MSG_RUN_FULL_RESTORE = 10;
+ // backup task state machine tick
+ static final int MSG_BACKUP_RESTORE_STEP = 20;
+ static final int MSG_OP_COMPLETE = 21;
+
// Timeout interval for deciding that a bind or clear-data has taken too long
static final long TIMEOUT_INTERVAL = 10 * 1000;
@@ -344,7 +348,16 @@
static final int OP_ACKNOWLEDGED = 1;
static final int OP_TIMEOUT = -1;
- final SparseIntArray mCurrentOperations = new SparseIntArray();
+ class Operation {
+ public int state;
+ public BackupRestoreTask callback;
+
+ Operation(int initialState, BackupRestoreTask callbackObj) {
+ state = initialState;
+ callback = callbackObj;
+ }
+ }
+ final SparseArray<Operation> mCurrentOperations = new SparseArray<Operation>();
final Object mCurrentOpLock = new Object();
final Random mTokenGenerator = new Random();
@@ -442,13 +455,16 @@
}
}
+ // At this point, we have started a new journal file, and the old
+ // file identity is being passed to the backup processing task.
+ // When it completes successfully, that old journal file will be
+ // deleted. If we crash prior to that, the old journal is parsed
+ // at next boot and the journaled requests fulfilled.
if (queue.size() > 0) {
- // At this point, we have started a new journal file, and the old
- // file identity is being passed to the backup processing thread.
- // When it completes successfully, that old journal file will be
- // deleted. If we crash prior to that, the old journal is parsed
- // at next boot and the journaled requests fulfilled.
- (new PerformBackupTask(transport, queue, oldJournal)).run();
+ // Spin up a backup state sequence and set it running
+ PerformBackupTask pbt = new PerformBackupTask(transport, queue, oldJournal);
+ Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt);
+ sendMessage(pbtMessage);
} else {
Slog.v(TAG, "Backup requested but nothing pending");
mWakelock.release();
@@ -456,6 +472,29 @@
break;
}
+ case MSG_BACKUP_RESTORE_STEP:
+ {
+ try {
+ BackupRestoreTask task = (BackupRestoreTask) msg.obj;
+ if (MORE_DEBUG) Slog.v(TAG, "Got next step for " + task + ", executing");
+ task.execute();
+ } catch (ClassCastException e) {
+ Slog.e(TAG, "Invalid backup task in flight, obj=" + msg.obj);
+ }
+ break;
+ }
+
+ case MSG_OP_COMPLETE:
+ {
+ try {
+ BackupRestoreTask task = (BackupRestoreTask) msg.obj;
+ task.operationComplete();
+ } catch (ClassCastException e) {
+ Slog.e(TAG, "Invalid completion in flight, obj=" + msg.obj);
+ }
+ break;
+ }
+
case MSG_RUN_FULL_BACKUP:
{
FullBackupParams params = (FullBackupParams)msg.obj;
@@ -469,9 +508,12 @@
{
RestoreParams params = (RestoreParams)msg.obj;
Slog.d(TAG, "MSG_RUN_RESTORE observer=" + params.observer);
- (new PerformRestoreTask(params.transport, params.observer,
+ PerformRestoreTask task = new PerformRestoreTask(
+ params.transport, params.observer,
params.token, params.pkgInfo, params.pmToken,
- params.needFullBackup, params.filterSet)).run();
+ params.needFullBackup, params.filterSet);
+ Message restoreMsg = obtainMessage(MSG_BACKUP_RESTORE_STEP, task);
+ sendMessage(restoreMsg);
break;
}
@@ -540,15 +582,7 @@
case MSG_TIMEOUT:
{
- synchronized (mCurrentOpLock) {
- final int token = msg.arg1;
- int state = mCurrentOperations.get(token, OP_TIMEOUT);
- if (state == OP_PENDING) {
- if (DEBUG) Slog.v(TAG, "TIMEOUT: token=" + token);
- mCurrentOperations.put(token, OP_TIMEOUT);
- }
- mCurrentOpLock.notifyAll();
- }
+ handleTimeout(msg.arg1, msg.obj);
break;
}
@@ -558,7 +592,7 @@
if (mActiveRestoreSession != null) {
// Client app left the restore session dangling. We know that it
// can't be in the middle of an actual restore operation because
- // those are executed serially on this same handler thread. Clean
+ // the timeout is suspended while a restore is in progress. Clean
// up now.
Slog.w(TAG, "Restore session timed out; aborting");
post(mActiveRestoreSession.new EndRestoreRunnable(
@@ -1113,12 +1147,14 @@
}
// Enqueue a new backup of every participant
- int N = mBackupParticipants.size();
- for (int i=0; i<N; i++) {
- int uid = mBackupParticipants.keyAt(i);
- HashSet<ApplicationInfo> participants = mBackupParticipants.valueAt(i);
- for (ApplicationInfo app: participants) {
- dataChangedImpl(app.packageName);
+ synchronized (mBackupParticipants) {
+ int N = mBackupParticipants.size();
+ for (int i=0; i<N; i++) {
+ int uid = mBackupParticipants.keyAt(i);
+ HashSet<ApplicationInfo> participants = mBackupParticipants.valueAt(i);
+ for (ApplicationInfo app: participants) {
+ dataChangedImpl(app.packageName);
+ }
}
}
}
@@ -1588,50 +1624,120 @@
}
// -----
- // Utility methods used by the asynchronous-with-timeout backup/restore operations
- boolean waitUntilOperationComplete(int token) {
- int finalState = OP_PENDING;
+ // Interface and methods used by the asynchronous-with-timeout backup/restore operations
+
+ interface BackupRestoreTask {
+ // Execute one tick of whatever state machine the task implements
+ void execute();
+
+ // An operation that wanted a callback has completed
+ void operationComplete();
+
+ // An operation that wanted a callback has timed out
+ void handleTimeout();
+ }
+
+ void prepareOperationTimeout(int token, long interval, BackupRestoreTask callback) {
+ if (MORE_DEBUG) Slog.v(TAG, "starting timeout: token=" + Integer.toHexString(token)
+ + " interval=" + interval);
synchronized (mCurrentOpLock) {
- try {
- while ((finalState = mCurrentOperations.get(token, OP_TIMEOUT)) == OP_PENDING) {
- try {
- mCurrentOpLock.wait();
- } catch (InterruptedException e) {}
+ mCurrentOperations.put(token, new Operation(OP_PENDING, callback));
+
+ Message msg = mBackupHandler.obtainMessage(MSG_TIMEOUT, token, 0, callback);
+ mBackupHandler.sendMessageDelayed(msg, interval);
+ }
+ }
+
+ // synchronous waiter case
+ boolean waitUntilOperationComplete(int token) {
+ if (MORE_DEBUG) Slog.i(TAG, "Blocking until operation complete for "
+ + Integer.toHexString(token));
+ int finalState = OP_PENDING;
+ Operation op = null;
+ synchronized (mCurrentOpLock) {
+ while (true) {
+ op = mCurrentOperations.get(token);
+ if (op == null) {
+ // mysterious disappearance: treat as success with no callback
+ break;
+ } else {
+ if (op.state == OP_PENDING) {
+ try {
+ mCurrentOpLock.wait();
+ } catch (InterruptedException e) {}
+ // When the wait is notified we loop around and recheck the current state
+ } else {
+ // No longer pending; we're done
+ finalState = op.state;
+ break;
+ }
}
- } catch (IndexOutOfBoundsException e) {
- // the operation has been mysteriously cleared from our
- // bookkeeping -- consider this a success and ignore it.
}
}
+
mBackupHandler.removeMessages(MSG_TIMEOUT);
if (MORE_DEBUG) Slog.v(TAG, "operation " + Integer.toHexString(token)
+ " complete: finalState=" + finalState);
return finalState == OP_ACKNOWLEDGED;
}
- void prepareOperationTimeout(int token, long interval) {
- if (MORE_DEBUG) Slog.v(TAG, "starting timeout: token=" + Integer.toHexString(token)
- + " interval=" + interval);
+ void handleTimeout(int token, Object obj) {
+ // Notify any synchronous waiters
+ Operation op = null;
synchronized (mCurrentOpLock) {
- mCurrentOperations.put(token, OP_PENDING);
- Message msg = mBackupHandler.obtainMessage(MSG_TIMEOUT, token, 0);
- mBackupHandler.sendMessageDelayed(msg, interval);
+ op = mCurrentOperations.get(token);
+ if (MORE_DEBUG) {
+ if (op == null) Slog.w(TAG, "Timeout of token " + Integer.toHexString(token)
+ + " but no op found");
+ }
+ int state = (op != null) ? op.state : OP_TIMEOUT;
+ if (state == OP_PENDING) {
+ if (DEBUG) Slog.v(TAG, "TIMEOUT: token=" + Integer.toHexString(token));
+ op.state = OP_TIMEOUT;
+ mCurrentOperations.put(token, op);
+ }
+ mCurrentOpLock.notifyAll();
+ }
+
+ // If there's a TimeoutHandler for this event, call it
+ if (op != null && op.callback != null) {
+ op.callback.handleTimeout();
}
}
// ----- Back up a set of applications via a worker thread -----
- class PerformBackupTask implements Runnable {
- private static final String TAG = "PerformBackupThread";
+ enum BackupState {
+ INITIAL,
+ RUNNING_QUEUE,
+ FINAL
+ }
+
+ class PerformBackupTask implements BackupRestoreTask {
+ private static final String TAG = "PerformBackupTask";
+
IBackupTransport mTransport;
ArrayList<BackupRequest> mQueue;
+ ArrayList<BackupRequest> mOriginalQueue;
File mStateDir;
File mJournal;
+ BackupState mCurrentState;
+
+ // carried information about the current in-flight operation
+ PackageInfo mCurrentPackage;
+ File mSavedStateName;
+ File mBackupDataName;
+ File mNewStateName;
+ ParcelFileDescriptor mSavedState;
+ ParcelFileDescriptor mBackupData;
+ ParcelFileDescriptor mNewState;
+ int mStatus;
+ boolean mFinished;
public PerformBackupTask(IBackupTransport transport, ArrayList<BackupRequest> queue,
File journal) {
mTransport = transport;
- mQueue = queue;
+ mOriginalQueue = queue;
mJournal = journal;
try {
@@ -1639,26 +1745,62 @@
} catch (RemoteException e) {
// can't happen; the transport is local
}
+
+ mCurrentState = BackupState.INITIAL;
+ mFinished = false;
}
- public void run() {
- int status = BackupConstants.TRANSPORT_OK;
- long startRealtime = SystemClock.elapsedRealtime();
+ // Main entry point: perform one chunk of work, updating the state as appropriate
+ // and reposting the next chunk to the primary backup handler thread.
+ @Override
+ public void execute() {
+ switch (mCurrentState) {
+ case INITIAL:
+ beginBackup();
+ break;
+
+ case RUNNING_QUEUE:
+ invokeNextAgent();
+ break;
+
+ case FINAL:
+ if (!mFinished) finalizeBackup();
+ else {
+ Slog.e(TAG, "Duplicate finish");
+ }
+ mFinished = true;
+ break;
+ }
+ }
+
+ // We're starting a backup pass. Initialize the transport and send
+ // the PM metadata blob if we haven't already.
+ void beginBackup() {
+ mStatus = BackupConstants.TRANSPORT_OK;
+
+ // Sanity check: if the queue is empty we have no work to do.
+ if (mOriginalQueue.isEmpty()) {
+ Slog.w(TAG, "Backup begun with an empty queue - nothing to do.");
+ return;
+ }
+
+ // We need to retain the original queue contents in case of transport
+ // failure, but we want a working copy that we can manipulate along
+ // the way.
+ mQueue = (ArrayList<BackupRequest>) mOriginalQueue.clone();
+
if (DEBUG) Slog.v(TAG, "Beginning backup of " + mQueue.size() + " targets");
- // Backups run at background priority
- Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
-
+ File pmState = new File(mStateDir, PACKAGE_MANAGER_SENTINEL);
try {
EventLog.writeEvent(EventLogTags.BACKUP_START, mTransport.transportDirName());
// If we haven't stored package manager metadata yet, we must init the transport.
- File pmState = new File(mStateDir, PACKAGE_MANAGER_SENTINEL);
- if (status == BackupConstants.TRANSPORT_OK && pmState.length() <= 0) {
+ if (mStatus == BackupConstants.TRANSPORT_OK && pmState.length() <= 0) {
Slog.i(TAG, "Initializing (wiping) backup state and transport storage");
resetBackupState(mStateDir); // Just to make sure.
- status = mTransport.initializeDevice();
- if (status == BackupConstants.TRANSPORT_OK) {
+ mStatus = mTransport.initializeDevice();
+ if (mStatus == BackupConstants.TRANSPORT_OK) {
EventLog.writeEvent(EventLogTags.BACKUP_INITIALIZE);
} else {
EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, "(initialize)");
@@ -1671,207 +1813,219 @@
// directly and use a synthetic BackupRequest. We always run this pass
// because it's cheap and this way we guarantee that we don't get out of
// step even if we're selecting among various transports at run time.
- if (status == BackupConstants.TRANSPORT_OK) {
+ if (mStatus == BackupConstants.TRANSPORT_OK) {
PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(
mPackageManager, allAgentPackages());
- status = processOneBackup(PACKAGE_MANAGER_SENTINEL,
+ mStatus = invokeAgentForBackup(PACKAGE_MANAGER_SENTINEL,
IBackupAgent.Stub.asInterface(pmAgent.onBind()), mTransport);
}
- if (status == BackupConstants.TRANSPORT_OK) {
- // Now run all the backups in our queue
- status = doQueuedBackups(mTransport);
- }
-
- if (status == BackupConstants.TRANSPORT_OK) {
- // Tell the transport to finish everything it has buffered
- status = mTransport.finishBackup();
- if (status == BackupConstants.TRANSPORT_OK) {
- int millis = (int) (SystemClock.elapsedRealtime() - startRealtime);
- EventLog.writeEvent(EventLogTags.BACKUP_SUCCESS, mQueue.size(), millis);
- } else {
- EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, "(finish)");
- Slog.e(TAG, "Transport error in finishBackup()");
- }
- }
-
- if (status == BackupConstants.TRANSPORT_NOT_INITIALIZED) {
- // The backend reports that our dataset has been wiped. We need to
- // reset all of our bookkeeping and instead run a new backup pass for
- // everything.
+ if (mStatus == BackupConstants.TRANSPORT_NOT_INITIALIZED) {
+ // The backend reports that our dataset has been wiped. Note this in
+ // the event log; the no-success code below will reset the backup
+ // state as well.
EventLog.writeEvent(EventLogTags.BACKUP_RESET, mTransport.transportDirName());
- resetBackupState(mStateDir);
}
} catch (Exception e) {
Slog.e(TAG, "Error in backup thread", e);
- status = BackupConstants.TRANSPORT_ERROR;
+ mStatus = BackupConstants.TRANSPORT_ERROR;
} finally {
- // If everything actually went through and this is the first time we've
- // done a backup, we can now record what the current backup dataset token
- // is.
- if ((mCurrentToken == 0) && (status == BackupConstants.TRANSPORT_OK)) {
- try {
- mCurrentToken = mTransport.getCurrentRestoreSet();
- } catch (RemoteException e) { /* cannot happen */ }
- writeRestoreTokens();
+ // If we've succeeded so far, invokeAgentForBackup() will have run the PM
+ // metadata and its completion/timeout callback will continue the state
+ // machine chain. If it failed that won't happen; we handle that now.
+ if (mStatus != BackupConstants.TRANSPORT_OK) {
+ // if things went wrong at this point, we need to
+ // restage everything and try again later.
+ resetBackupState(mStateDir); // Just to make sure.
+ executeNextState(BackupState.FINAL);
}
-
- // If things went wrong, we need to re-stage the apps we had expected
- // to be backing up in this pass. This journals the package names in
- // the current active pending-backup file, not in the we are holding
- // here in mJournal.
- if (status != BackupConstants.TRANSPORT_OK) {
- Slog.w(TAG, "Backup pass unsuccessful, restaging");
- for (BackupRequest req : mQueue) {
- dataChangedImpl(req.packageName);
- }
-
- // We also want to reset the backup schedule based on whatever
- // the transport suggests by way of retry/backoff time.
- try {
- startBackupAlarmsLocked(mTransport.requestBackupTime());
- } catch (RemoteException e) { /* cannot happen */ }
- }
-
- // Either backup was successful, in which case we of course do not need
- // this pass's journal any more; or it failed, in which case we just
- // re-enqueued all of these packages in the current active journal.
- // Either way, we no longer need this pass's journal.
- if (mJournal != null && !mJournal.delete()) {
- Slog.e(TAG, "Unable to remove backup journal file " + mJournal);
- }
-
- // Only once we're entirely finished do we release the wakelock
- if (status == BackupConstants.TRANSPORT_NOT_INITIALIZED) {
- backupNow();
- }
-
- mWakelock.release();
}
}
- private int doQueuedBackups(IBackupTransport transport) {
- for (BackupRequest request : mQueue) {
- Slog.d(TAG, "starting agent for backup of " + request);
+ // Transport has been initialized and the PM metadata submitted successfully
+ // if that was warranted. Now we process the single next thing in the queue.
+ void invokeNextAgent() {
+ mStatus = BackupConstants.TRANSPORT_OK;
- // Verify that the requested app exists; it might be something that
- // requested a backup but was then uninstalled. The request was
- // journalled and rather than tamper with the journal it's safer
- // to sanity-check here. This also gives us the classname of the
- // package's backup agent.
- PackageInfo pkg;
- try {
- pkg = mPackageManager.getPackageInfo(request.packageName, 0);
- } catch (NameNotFoundException e) {
- Slog.d(TAG, "Package does not exist; skipping");
- continue;
- }
+ // Sanity check that we have work to do. If not, skip to the end where
+ // we reestablish the wakelock invariants etc.
+ if (mQueue.isEmpty()) {
+ Slog.e(TAG, "Running queue but it's empty!");
+ executeNextState(BackupState.FINAL);
+ return;
+ }
+
+ // pop the entry we're going to process on this step
+ BackupRequest request = mQueue.get(0);
+ mQueue.remove(0);
+
+ Slog.d(TAG, "starting agent for backup of " + request);
+
+ // Verify that the requested app exists; it might be something that
+ // requested a backup but was then uninstalled. The request was
+ // journalled and rather than tamper with the journal it's safer
+ // to sanity-check here. This also gives us the classname of the
+ // package's backup agent.
+ try {
+ mCurrentPackage = mPackageManager.getPackageInfo(request.packageName,
+ PackageManager.GET_SIGNATURES);
IBackupAgent agent = null;
try {
- mWakelock.setWorkSource(new WorkSource(pkg.applicationInfo.uid));
- agent = bindToAgentSynchronous(pkg.applicationInfo,
+ mWakelock.setWorkSource(new WorkSource(mCurrentPackage.applicationInfo.uid));
+ agent = bindToAgentSynchronous(mCurrentPackage.applicationInfo,
IApplicationThread.BACKUP_MODE_INCREMENTAL);
if (agent != null) {
- int result = processOneBackup(request.packageName, agent, transport);
- if (result != BackupConstants.TRANSPORT_OK) return result;
+ mStatus = invokeAgentForBackup(request.packageName, agent, mTransport);
+ // at this point we'll either get a completion callback from the
+ // agent, or a timeout message on the main handler. either way, we're
+ // done here as long as we're successful so far.
+ } else {
+ // Timeout waiting for the agent
+ mStatus = BackupConstants.AGENT_ERROR;
}
} catch (SecurityException ex) {
// Try for the next one.
Slog.d(TAG, "error in bind/backup", ex);
- } finally {
- try { // unbind even on timeout, just in case
- mActivityManager.unbindBackupAgent(pkg.applicationInfo);
- } catch (RemoteException e) {}
+ mStatus = BackupConstants.AGENT_ERROR;
+ }
+ } catch (NameNotFoundException e) {
+ Slog.d(TAG, "Package does not exist; skipping");
+ } finally {
+ mWakelock.setWorkSource(null);
+
+ // If there was an agent error, no timeout/completion handling will occur.
+ // That means we need to deal with the next state ourselves.
+ if (mStatus != BackupConstants.TRANSPORT_OK) {
+ BackupState nextState = BackupState.RUNNING_QUEUE;
+
+ // An agent-level failure means we reenqueue this one agent for
+ // a later retry, but otherwise proceed normally.
+ if (mStatus == BackupConstants.AGENT_ERROR) {
+ if (MORE_DEBUG) Slog.i(TAG, "Agent failure for " + request.packageName
+ + " - restaging");
+ dataChangedImpl(request.packageName);
+ mStatus = BackupConstants.TRANSPORT_OK;
+ if (mQueue.isEmpty()) nextState = BackupState.FINAL;
+ } else if (mStatus != BackupConstants.TRANSPORT_OK) {
+ // Transport-level failure means we reenqueue everything
+ revertAndEndBackup();
+ nextState = BackupState.FINAL;
+ }
+
+ executeNextState(nextState);
}
}
-
- mWakelock.setWorkSource(null);
-
- return BackupConstants.TRANSPORT_OK;
}
- private int processOneBackup(String packageName, IBackupAgent agent,
+ void finalizeBackup() {
+ // Either backup was successful, in which case we of course do not need
+ // this pass's journal any more; or it failed, in which case we just
+ // re-enqueued all of these packages in the current active journal.
+ // Either way, we no longer need this pass's journal.
+ if (mJournal != null && !mJournal.delete()) {
+ Slog.e(TAG, "Unable to remove backup journal file " + mJournal);
+ }
+
+ // If everything actually went through and this is the first time we've
+ // done a backup, we can now record what the current backup dataset token
+ // is.
+ if ((mCurrentToken == 0) && (mStatus == BackupConstants.TRANSPORT_OK)) {
+ try {
+ mCurrentToken = mTransport.getCurrentRestoreSet();
+ } catch (RemoteException e) {} // can't happen
+ writeRestoreTokens();
+ }
+
+ // Set up the next backup pass
+ if (mStatus == BackupConstants.TRANSPORT_NOT_INITIALIZED) {
+ backupNow();
+ }
+
+ // Only once we're entirely finished do we release the wakelock
+ Slog.i(TAG, "Backup pass finished.");
+ mWakelock.release();
+ }
+
+ // Invoke an agent's doBackup() and start a timeout message spinning on the main
+ // handler in case it doesn't get back to us.
+ int invokeAgentForBackup(String packageName, IBackupAgent agent,
IBackupTransport transport) {
if (DEBUG) Slog.d(TAG, "processOneBackup doBackup() on " + packageName);
- File savedStateName = new File(mStateDir, packageName);
- File backupDataName = new File(mDataDir, packageName + ".data");
- File newStateName = new File(mStateDir, packageName + ".new");
+ mSavedStateName = new File(mStateDir, packageName);
+ mBackupDataName = new File(mDataDir, packageName + ".data");
+ mNewStateName = new File(mStateDir, packageName + ".new");
- ParcelFileDescriptor savedState = null;
- ParcelFileDescriptor backupData = null;
- ParcelFileDescriptor newState = null;
+ mSavedState = null;
+ mBackupData = null;
+ mNewState = null;
- PackageInfo packInfo;
final int token = generateToken();
try {
// Look up the package info & signatures. This is first so that if it
// throws an exception, there's no file setup yet that would need to
// be unraveled.
if (packageName.equals(PACKAGE_MANAGER_SENTINEL)) {
- // The metadata 'package' is synthetic
- packInfo = new PackageInfo();
- packInfo.packageName = packageName;
- } else {
- packInfo = mPackageManager.getPackageInfo(packageName,
- PackageManager.GET_SIGNATURES);
+ // The metadata 'package' is synthetic; construct one and make
+ // sure our global state is pointed at it
+ mCurrentPackage = new PackageInfo();
+ mCurrentPackage.packageName = packageName;
}
// In a full backup, we pass a null ParcelFileDescriptor as
// the saved-state "file". This is by definition an incremental,
// so we build a saved state file to pass.
- savedState = ParcelFileDescriptor.open(savedStateName,
+ mSavedState = ParcelFileDescriptor.open(mSavedStateName,
ParcelFileDescriptor.MODE_READ_ONLY |
ParcelFileDescriptor.MODE_CREATE); // Make an empty file if necessary
- backupData = ParcelFileDescriptor.open(backupDataName,
+ mBackupData = ParcelFileDescriptor.open(mBackupDataName,
ParcelFileDescriptor.MODE_READ_WRITE |
ParcelFileDescriptor.MODE_CREATE |
ParcelFileDescriptor.MODE_TRUNCATE);
- newState = ParcelFileDescriptor.open(newStateName,
+ mNewState = ParcelFileDescriptor.open(mNewStateName,
ParcelFileDescriptor.MODE_READ_WRITE |
ParcelFileDescriptor.MODE_CREATE |
ParcelFileDescriptor.MODE_TRUNCATE);
// Initiate the target's backup pass
- prepareOperationTimeout(token, TIMEOUT_BACKUP_INTERVAL);
- agent.doBackup(savedState, backupData, newState, token, mBackupManagerBinder);
- boolean success = waitUntilOperationComplete(token);
-
- if (!success) {
- // timeout -- bail out into the failed-transaction logic
- throw new RuntimeException("Backup timeout");
- }
-
- logBackupComplete(packageName);
- if (DEBUG) Slog.v(TAG, "doBackup() success");
+ prepareOperationTimeout(token, TIMEOUT_BACKUP_INTERVAL, this);
+ agent.doBackup(mSavedState, mBackupData, mNewState, token, mBackupManagerBinder);
} catch (Exception e) {
- Slog.e(TAG, "Error backing up " + packageName, e);
- EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, packageName, e.toString());
- backupDataName.delete();
- newStateName.delete();
- return BackupConstants.TRANSPORT_ERROR;
- } finally {
- try { if (savedState != null) savedState.close(); } catch (IOException e) {}
- try { if (backupData != null) backupData.close(); } catch (IOException e) {}
- try { if (newState != null) newState.close(); } catch (IOException e) {}
- savedState = backupData = newState = null;
- synchronized (mCurrentOpLock) {
- mCurrentOperations.clear();
- }
+ Slog.e(TAG, "Error invoking for backup on " + packageName);
+ EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, packageName,
+ e.toString());
+ agentErrorCleanup();
+ return BackupConstants.AGENT_ERROR;
}
- // Now propagate the newly-backed-up data to the transport
- int result = BackupConstants.TRANSPORT_OK;
+ // At this point the agent is off and running. The next thing to happen will
+ // either be a callback from the agent, at which point we'll process its data
+ // for transport, or a timeout. Either way the next phase will happen in
+ // response to the TimeoutHandler interface callbacks.
+ return BackupConstants.TRANSPORT_OK;
+ }
+
+ @Override
+ public void operationComplete() {
+ // Okay, the agent successfully reported back to us. Spin the data off to the
+ // transport and proceed with the next stage.
+ if (MORE_DEBUG) Slog.v(TAG, "operationComplete(): sending data to transport for "
+ + mCurrentPackage.packageName);
+ mBackupHandler.removeMessages(MSG_TIMEOUT);
+ clearAgentState();
+
+ ParcelFileDescriptor backupData = null;
+ mStatus = BackupConstants.TRANSPORT_OK;
try {
- int size = (int) backupDataName.length();
+ int size = (int) mBackupDataName.length();
if (size > 0) {
- if (result == BackupConstants.TRANSPORT_OK) {
- backupData = ParcelFileDescriptor.open(backupDataName,
+ if (mStatus == BackupConstants.TRANSPORT_OK) {
+ backupData = ParcelFileDescriptor.open(mBackupDataName,
ParcelFileDescriptor.MODE_READ_ONLY);
- result = transport.performBackup(packInfo, backupData);
+ mStatus = mTransport.performBackup(mCurrentPackage, backupData);
}
// TODO - We call finishBackup() for each application backed up, because
@@ -1879,8 +2033,8 @@
// hold off on finishBackup() until the end, which implies holding off on
// renaming *all* the output state files (see below) until that happens.
- if (result == BackupConstants.TRANSPORT_OK) {
- result = transport.finishBackup();
+ if (mStatus == BackupConstants.TRANSPORT_OK) {
+ mStatus = mTransport.finishBackup();
}
} else {
if (DEBUG) Slog.i(TAG, "no backup data written; not calling transport");
@@ -1889,22 +2043,102 @@
// After successful transport, delete the now-stale data
// and juggle the files so that next time we supply the agent
// with the new state file it just created.
- if (result == BackupConstants.TRANSPORT_OK) {
- backupDataName.delete();
- newStateName.renameTo(savedStateName);
- EventLog.writeEvent(EventLogTags.BACKUP_PACKAGE, packageName, size);
+ if (mStatus == BackupConstants.TRANSPORT_OK) {
+ mBackupDataName.delete();
+ mNewStateName.renameTo(mSavedStateName);
+ EventLog.writeEvent(EventLogTags.BACKUP_PACKAGE,
+ mCurrentPackage.packageName, size);
+ logBackupComplete(mCurrentPackage.packageName);
} else {
- EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, packageName);
+ EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE,
+ mCurrentPackage.packageName);
}
} catch (Exception e) {
- Slog.e(TAG, "Transport error backing up " + packageName, e);
- EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, packageName);
- result = BackupConstants.TRANSPORT_ERROR;
+ Slog.e(TAG, "Transport error backing up " + mCurrentPackage.packageName, e);
+ EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE,
+ mCurrentPackage.packageName);
+ mStatus = BackupConstants.TRANSPORT_ERROR;
} finally {
try { if (backupData != null) backupData.close(); } catch (IOException e) {}
}
- return result;
+ // If we encountered an error here it's a transport-level failure. That
+ // means we need to halt everything and reschedule everything for next time.
+ final BackupState nextState;
+ if (mStatus != BackupConstants.TRANSPORT_OK) {
+ revertAndEndBackup();
+ nextState = BackupState.FINAL;
+ } else {
+ // Success! Proceed with the next app if any, otherwise we're done.
+ nextState = (mQueue.isEmpty()) ? BackupState.FINAL : BackupState.RUNNING_QUEUE;
+ }
+
+ executeNextState(nextState);
+ }
+
+ @Override
+ public void handleTimeout() {
+ // Whoops, the current agent timed out running doBackup(). Tidy up and restage
+ // it for the next time we run a backup pass.
+ // !!! TODO: keep track of failure counts per agent, and blacklist those which
+ // fail repeatedly (i.e. have proved themselves to be buggy).
+ Slog.e(TAG, "Timeout backing up " + mCurrentPackage.packageName);
+ EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, mCurrentPackage.packageName,
+ "timeout");
+ agentErrorCleanup();
+ dataChangedImpl(mCurrentPackage.packageName);
+ }
+
+ void revertAndEndBackup() {
+ if (MORE_DEBUG) Slog.i(TAG, "Reverting backup queue - restaging everything");
+ for (BackupRequest request : mOriginalQueue) {
+ dataChangedImpl(request.packageName);
+ }
+ // We also want to reset the backup schedule based on whatever
+ // the transport suggests by way of retry/backoff time.
+ restartBackupAlarm();
+ }
+
+ void agentErrorCleanup() {
+ mBackupDataName.delete();
+ mNewStateName.delete();
+ clearAgentState();
+
+ executeNextState(mQueue.isEmpty() ? BackupState.FINAL : BackupState.RUNNING_QUEUE);
+ }
+
+ // Cleanup common to both success and failure cases
+ void clearAgentState() {
+ try { if (mSavedState != null) mSavedState.close(); } catch (IOException e) {}
+ try { if (mBackupData != null) mBackupData.close(); } catch (IOException e) {}
+ try { if (mNewState != null) mNewState.close(); } catch (IOException e) {}
+ mSavedState = mBackupData = mNewState = null;
+ synchronized (mCurrentOpLock) {
+ mCurrentOperations.clear();
+ }
+
+ // If this was a pseudopackage there's no associated Activity Manager state
+ if (mCurrentPackage.applicationInfo != null) {
+ try { // unbind even on timeout, just in case
+ mActivityManager.unbindBackupAgent(mCurrentPackage.applicationInfo);
+ } catch (RemoteException e) {}
+ }
+ }
+
+ void restartBackupAlarm() {
+ synchronized (mQueueLock) {
+ try {
+ startBackupAlarmsLocked(mTransport.requestBackupTime());
+ } catch (RemoteException e) { /* cannot happen */ }
+ }
+ }
+
+ void executeNextState(BackupState nextState) {
+ if (MORE_DEBUG) Slog.i(TAG, " => executing next step on "
+ + this + " nextState=" + nextState);
+ mCurrentState = nextState;
+ Message msg = mBackupHandler.obtainMessage(MSG_BACKUP_RESTORE_STEP, this);
+ mBackupHandler.sendMessage(msg);
}
}
@@ -1959,7 +2193,7 @@
}
if (DEBUG) Slog.d(TAG, "Calling doFullBackup() on " + mPackage.packageName);
- prepareOperationTimeout(mToken, TIMEOUT_FULL_BACKUP_INTERVAL);
+ prepareOperationTimeout(mToken, TIMEOUT_FULL_BACKUP_INTERVAL, null);
mAgent.doFullBackup(mPipe, mToken, mBackupManagerBinder);
} catch (IOException e) {
Slog.e(TAG, "Error running full backup for " + mPackage.packageName);
@@ -2320,7 +2554,7 @@
sendOnBackupPackage("Shared storage");
final int token = generateToken();
- prepareOperationTimeout(token, TIMEOUT_SHARED_BACKUP_INTERVAL);
+ prepareOperationTimeout(token, TIMEOUT_SHARED_BACKUP_INTERVAL, null);
agent.doFullBackup(mOutputFile, token, mBackupManagerBinder);
if (!waitUntilOperationComplete(token)) {
Slog.e(TAG, "Full backup failed on shared storage");
@@ -2899,8 +3133,7 @@
try {
if (DEBUG) Slog.d(TAG, "Invoking agent to restore file "
+ info.path);
- prepareOperationTimeout(token,
- TIMEOUT_FULL_BACKUP_INTERVAL);
+ prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL, null);
// fire up the app's agent listening on the socket. If
// the agent is running in the system process we can't
// just invoke it asynchronously, so we provide a thread
@@ -3693,7 +3926,15 @@
return true;
}
- class PerformRestoreTask implements Runnable {
+ enum RestoreState {
+ INITIAL,
+ DOWNLOAD_DATA,
+ PM_METADATA,
+ RUNNING_QUEUE,
+ FINAL
+ }
+
+ class PerformRestoreTask implements BackupRestoreTask {
private IBackupTransport mTransport;
private IRestoreObserver mObserver;
private long mToken;
@@ -3702,6 +3943,21 @@
private int mPmToken;
private boolean mNeedFullBackup;
private HashSet<String> mFilterSet;
+ private long mStartRealtime;
+ private PackageManagerBackupAgent mPmAgent;
+ private List<PackageInfo> mAgentPackages;
+ private ArrayList<PackageInfo> mRestorePackages;
+ private RestoreState mCurrentState;
+ private int mCount;
+ private boolean mFinished;
+ private int mStatus;
+ private File mBackupDataName;
+ private File mNewStateName;
+ private File mSavedStateName;
+ private ParcelFileDescriptor mBackupData;
+ private ParcelFileDescriptor mNewState;
+ private PackageInfo mCurrentPackage;
+
class RestoreRequest {
public PackageInfo app;
@@ -3716,6 +3972,10 @@
PerformRestoreTask(IBackupTransport transport, IRestoreObserver observer,
long restoreSetToken, PackageInfo targetPackage, int pmToken,
boolean needFullBackup, String[] filterSet) {
+ mCurrentState = RestoreState.INITIAL;
+ mFinished = false;
+ mPmAgent = null;
+
mTransport = transport;
mObserver = observer;
mToken = restoreSetToken;
@@ -3739,50 +3999,79 @@
}
}
- public void run() {
- long startRealtime = SystemClock.elapsedRealtime();
- if (DEBUG) Slog.v(TAG, "Beginning restore process mTransport=" + mTransport
- + " mObserver=" + mObserver + " mToken=" + Long.toHexString(mToken)
- + " mTargetPackage=" + mTargetPackage + " mFilterSet=" + mFilterSet
- + " mPmToken=" + mPmToken);
+ // Execute one tick of whatever state machine the task implements
+ @Override
+ public void execute() {
+ if (MORE_DEBUG) Slog.v(TAG, "*** Executing restore step: " + mCurrentState);
+ switch (mCurrentState) {
+ case INITIAL:
+ beginRestore();
+ break;
- PackageManagerBackupAgent pmAgent = null;
- int error = -1; // assume error
+ case DOWNLOAD_DATA:
+ downloadRestoreData();
+ break;
- // build the set of apps to restore
+ case PM_METADATA:
+ restorePmMetadata();
+ break;
+
+ case RUNNING_QUEUE:
+ restoreNextAgent();
+ break;
+
+ case FINAL:
+ if (!mFinished) finalizeRestore();
+ else {
+ Slog.e(TAG, "Duplicate finish");
+ }
+ mFinished = true;
+ break;
+ }
+ }
+
+ // Initialize and set up for the PM metadata restore, which comes first
+ void beginRestore() {
+ // Don't account time doing the restore as inactivity of the app
+ // that has opened a restore session.
+ mBackupHandler.removeMessages(MSG_RESTORE_TIMEOUT);
+
+ // Assume error until we successfully init everything
+ mStatus = BackupConstants.TRANSPORT_ERROR;
+
try {
// TODO: Log this before getAvailableRestoreSets, somehow
EventLog.writeEvent(EventLogTags.RESTORE_START, mTransport.transportDirName(), mToken);
// Get the list of all packages which have backup enabled.
// (Include the Package Manager metadata pseudo-package first.)
- ArrayList<PackageInfo> restorePackages = new ArrayList<PackageInfo>();
+ mRestorePackages = new ArrayList<PackageInfo>();
PackageInfo omPackage = new PackageInfo();
omPackage.packageName = PACKAGE_MANAGER_SENTINEL;
- restorePackages.add(omPackage);
+ mRestorePackages.add(omPackage);
- List<PackageInfo> agentPackages = allAgentPackages();
+ mAgentPackages = allAgentPackages();
if (mTargetPackage == null) {
// if there's a filter set, strip out anything that isn't
// present before proceeding
if (mFilterSet != null) {
- for (int i = agentPackages.size() - 1; i >= 0; i--) {
- final PackageInfo pkg = agentPackages.get(i);
+ for (int i = mAgentPackages.size() - 1; i >= 0; i--) {
+ final PackageInfo pkg = mAgentPackages.get(i);
if (! mFilterSet.contains(pkg.packageName)) {
- agentPackages.remove(i);
+ mAgentPackages.remove(i);
}
}
- if (DEBUG) {
+ if (MORE_DEBUG) {
Slog.i(TAG, "Post-filter package set for restore:");
- for (PackageInfo p : agentPackages) {
+ for (PackageInfo p : mAgentPackages) {
Slog.i(TAG, " " + p);
}
}
}
- restorePackages.addAll(agentPackages);
+ mRestorePackages.addAll(mAgentPackages);
} else {
// Just one package to attempt restore of
- restorePackages.add(mTargetPackage);
+ mRestorePackages.add(mTargetPackage);
}
// let the observer know that we're running
@@ -3790,306 +4079,412 @@
try {
// !!! TODO: get an actual count from the transport after
// its startRestore() runs?
- mObserver.restoreStarting(restorePackages.size());
+ mObserver.restoreStarting(mRestorePackages.size());
} catch (RemoteException e) {
Slog.d(TAG, "Restore observer died at restoreStarting");
mObserver = null;
}
}
+ } catch (RemoteException e) {
+ // Something has gone catastrophically wrong with the transport
+ Slog.e(TAG, "Error communicating with transport for restore");
+ executeNextState(RestoreState.FINAL);
+ return;
+ }
- if (mTransport.startRestore(mToken, restorePackages.toArray(new PackageInfo[0])) !=
- BackupConstants.TRANSPORT_OK) {
+ mStatus = BackupConstants.TRANSPORT_OK;
+ executeNextState(RestoreState.DOWNLOAD_DATA);
+ }
+
+ void downloadRestoreData() {
+ // Note that the download phase can be very time consuming, but we're executing
+ // it inline here on the looper. This is "okay" because it is not calling out to
+ // third party code; the transport is "trusted," and so we assume it is being a
+ // good citizen and timing out etc when appropriate.
+ //
+ // TODO: when appropriate, move the download off the looper and rearrange the
+ // error handling around that.
+ try {
+ mStatus = mTransport.startRestore(mToken,
+ mRestorePackages.toArray(new PackageInfo[0]));
+ if (mStatus != BackupConstants.TRANSPORT_OK) {
Slog.e(TAG, "Error starting restore operation");
EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
+ executeNextState(RestoreState.FINAL);
return;
}
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error communicating with transport for restore");
+ EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
+ mStatus = BackupConstants.TRANSPORT_ERROR;
+ executeNextState(RestoreState.FINAL);
+ return;
+ }
+ // Successful download of the data to be parceled out to the apps, so off we go.
+ executeNextState(RestoreState.PM_METADATA);
+ }
+
+ void restorePmMetadata() {
+ try {
String packageName = mTransport.nextRestorePackage();
if (packageName == null) {
Slog.e(TAG, "Error getting first restore package");
EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
+ mStatus = BackupConstants.TRANSPORT_ERROR;
+ executeNextState(RestoreState.FINAL);
return;
} else if (packageName.equals("")) {
Slog.i(TAG, "No restore data available");
- int millis = (int) (SystemClock.elapsedRealtime() - startRealtime);
+ int millis = (int) (SystemClock.elapsedRealtime() - mStartRealtime);
EventLog.writeEvent(EventLogTags.RESTORE_SUCCESS, 0, millis);
+ mStatus = BackupConstants.TRANSPORT_OK;
+ executeNextState(RestoreState.FINAL);
return;
} else if (!packageName.equals(PACKAGE_MANAGER_SENTINEL)) {
Slog.e(TAG, "Expected restore data for \"" + PACKAGE_MANAGER_SENTINEL
- + "\", found only \"" + packageName + "\"");
+ + "\", found only \"" + packageName + "\"");
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, PACKAGE_MANAGER_SENTINEL,
"Package manager data missing");
+ executeNextState(RestoreState.FINAL);
return;
}
// Pull the Package Manager metadata from the restore set first
- pmAgent = new PackageManagerBackupAgent(
- mPackageManager, agentPackages);
- processOneRestore(omPackage, 0, IBackupAgent.Stub.asInterface(pmAgent.onBind()),
+ PackageInfo omPackage = new PackageInfo();
+ omPackage.packageName = PACKAGE_MANAGER_SENTINEL;
+ mPmAgent = new PackageManagerBackupAgent(
+ mPackageManager, mAgentPackages);
+ initiateOneRestore(omPackage, 0, IBackupAgent.Stub.asInterface(mPmAgent.onBind()),
mNeedFullBackup);
+ // The PM agent called operationComplete() already, because our invocation
+ // of it is process-local and therefore synchronous. That means that a
+ // RUNNING_QUEUE message is already enqueued. Only if we're unable to
+ // proceed with running the queue do we remove that pending message and
+ // jump straight to the FINAL state.
// Verify that the backup set includes metadata. If not, we can't do
// signature/version verification etc, so we simply do not proceed with
// the restore operation.
- if (!pmAgent.hasMetadata()) {
+ if (!mPmAgent.hasMetadata()) {
Slog.e(TAG, "No restore metadata available, so not restoring settings");
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, PACKAGE_MANAGER_SENTINEL,
- "Package manager restore metadata missing");
+ "Package manager restore metadata missing");
+ mStatus = BackupConstants.TRANSPORT_ERROR;
+ mBackupHandler.removeMessages(MSG_BACKUP_RESTORE_STEP, this);
+ executeNextState(RestoreState.FINAL);
return;
}
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error communicating with transport for restore");
+ EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
+ mStatus = BackupConstants.TRANSPORT_ERROR;
+ mBackupHandler.removeMessages(MSG_BACKUP_RESTORE_STEP, this);
+ executeNextState(RestoreState.FINAL);
+ return;
+ }
- int count = 0;
- for (;;) {
- packageName = mTransport.nextRestorePackage();
+ // Metadata is intact, so we can now run the restore queue. If we get here,
+ // we have already enqueued the necessary next-step message on the looper.
+ }
- if (packageName == null) {
- Slog.e(TAG, "Error getting next restore package");
- EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
- return;
- } else if (packageName.equals("")) {
- if (DEBUG) Slog.v(TAG, "No next package, finishing restore");
- break;
- }
+ void restoreNextAgent() {
+ try {
+ String packageName = mTransport.nextRestorePackage();
- if (mObserver != null) {
- try {
- mObserver.onUpdate(count, packageName);
- } catch (RemoteException e) {
- Slog.d(TAG, "Restore observer died in onUpdate");
- mObserver = null;
- }
- }
-
- Metadata metaInfo = pmAgent.getRestoredMetadata(packageName);
- if (metaInfo == null) {
- Slog.e(TAG, "Missing metadata for " + packageName);
- EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
- "Package metadata missing");
- continue;
- }
-
- PackageInfo packageInfo;
- try {
- int flags = PackageManager.GET_SIGNATURES;
- packageInfo = mPackageManager.getPackageInfo(packageName, flags);
- } catch (NameNotFoundException e) {
- Slog.e(TAG, "Invalid package restoring data", e);
- EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
- "Package missing on device");
- continue;
- }
-
- if (metaInfo.versionCode > packageInfo.versionCode) {
- // Data is from a "newer" version of the app than we have currently
- // installed. If the app has not declared that it is prepared to
- // handle this case, we do not attempt the restore.
- if ((packageInfo.applicationInfo.flags
- & ApplicationInfo.FLAG_RESTORE_ANY_VERSION) == 0) {
- String message = "Version " + metaInfo.versionCode
- + " > installed version " + packageInfo.versionCode;
- Slog.w(TAG, "Package " + packageName + ": " + message);
- EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
- packageName, message);
- continue;
- } else {
- if (DEBUG) Slog.v(TAG, "Version " + metaInfo.versionCode
- + " > installed " + packageInfo.versionCode
- + " but restoreAnyVersion");
- }
- }
-
- if (!signaturesMatch(metaInfo.signatures, packageInfo)) {
- Slog.w(TAG, "Signature mismatch restoring " + packageName);
- EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
- "Signature mismatch");
- continue;
- }
-
- if (DEBUG) Slog.v(TAG, "Package " + packageName
- + " restore version [" + metaInfo.versionCode
- + "] is compatible with installed version ["
- + packageInfo.versionCode + "]");
-
- // Then set up and bind the agent
- IBackupAgent agent = bindToAgentSynchronous(
- packageInfo.applicationInfo,
- IApplicationThread.BACKUP_MODE_INCREMENTAL);
- if (agent == null) {
- Slog.w(TAG, "Can't find backup agent for " + packageName);
- EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
- "Restore agent missing");
- continue;
- }
-
- // And then finally run the restore on this agent
- try {
- processOneRestore(packageInfo, metaInfo.versionCode, agent,
- mNeedFullBackup);
- ++count;
- } finally {
- // unbind and tidy up even on timeout or failure, just in case
- mActivityManager.unbindBackupAgent(packageInfo.applicationInfo);
-
- // The agent was probably running with a stub Application object,
- // which isn't a valid run mode for the main app logic. Shut
- // down the app so that next time it's launched, it gets the
- // usual full initialization. Note that this is only done for
- // full-system restores: when a single app has requested a restore,
- // it is explicitly not killed following that operation.
- if (mTargetPackage == null && (packageInfo.applicationInfo.flags
- & ApplicationInfo.FLAG_KILL_AFTER_RESTORE) != 0) {
- if (DEBUG) Slog.d(TAG, "Restore complete, killing host process of "
- + packageInfo.applicationInfo.processName);
- mActivityManager.killApplicationProcess(
- packageInfo.applicationInfo.processName,
- packageInfo.applicationInfo.uid);
- }
- }
- }
-
- // if we get this far, report success to the observer
- error = 0;
- int millis = (int) (SystemClock.elapsedRealtime() - startRealtime);
- EventLog.writeEvent(EventLogTags.RESTORE_SUCCESS, count, millis);
- } catch (Exception e) {
- Slog.e(TAG, "Error in restore thread", e);
- } finally {
- if (DEBUG) Slog.d(TAG, "finishing restore mObserver=" + mObserver);
-
- try {
- mTransport.finishRestore();
- } catch (RemoteException e) {
- Slog.e(TAG, "Error finishing restore", e);
+ if (packageName == null) {
+ Slog.e(TAG, "Error getting next restore package");
+ EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
+ executeNextState(RestoreState.FINAL);
+ return;
+ } else if (packageName.equals("")) {
+ if (DEBUG) Slog.v(TAG, "No next package, finishing restore");
+ int millis = (int) (SystemClock.elapsedRealtime() - mStartRealtime);
+ EventLog.writeEvent(EventLogTags.RESTORE_SUCCESS, mCount, millis);
+ executeNextState(RestoreState.FINAL);
+ return;
}
if (mObserver != null) {
try {
- mObserver.restoreFinished(error);
+ mObserver.onUpdate(mCount, packageName);
} catch (RemoteException e) {
- Slog.d(TAG, "Restore observer died at restoreFinished");
+ Slog.d(TAG, "Restore observer died in onUpdate");
+ mObserver = null;
}
}
- // If this was a restoreAll operation, record that this was our
- // ancestral dataset, as well as the set of apps that are possibly
- // restoreable from the dataset
- if (mTargetPackage == null && pmAgent != null) {
- mAncestralPackages = pmAgent.getRestoredPackages();
- mAncestralToken = mToken;
- writeRestoreTokens();
+ Metadata metaInfo = mPmAgent.getRestoredMetadata(packageName);
+ if (metaInfo == null) {
+ Slog.e(TAG, "Missing metadata for " + packageName);
+ EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
+ "Package metadata missing");
+ executeNextState(RestoreState.RUNNING_QUEUE);
+ return;
}
- // We must under all circumstances tell the Package Manager to
- // proceed with install notifications if it's waiting for us.
- if (mPmToken > 0) {
- if (DEBUG) Slog.v(TAG, "finishing PM token " + mPmToken);
- try {
- mPackageManagerBinder.finishPackageInstall(mPmToken);
- } catch (RemoteException e) { /* can't happen */ }
+ PackageInfo packageInfo;
+ try {
+ int flags = PackageManager.GET_SIGNATURES;
+ packageInfo = mPackageManager.getPackageInfo(packageName, flags);
+ } catch (NameNotFoundException e) {
+ Slog.e(TAG, "Invalid package restoring data", e);
+ EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
+ "Package missing on device");
+ executeNextState(RestoreState.RUNNING_QUEUE);
+ return;
}
- // Furthermore we need to reset the session timeout clock
- mBackupHandler.removeMessages(MSG_RESTORE_TIMEOUT);
- mBackupHandler.sendEmptyMessageDelayed(MSG_RESTORE_TIMEOUT,
- TIMEOUT_RESTORE_INTERVAL);
+ if (metaInfo.versionCode > packageInfo.versionCode) {
+ // Data is from a "newer" version of the app than we have currently
+ // installed. If the app has not declared that it is prepared to
+ // handle this case, we do not attempt the restore.
+ if ((packageInfo.applicationInfo.flags
+ & ApplicationInfo.FLAG_RESTORE_ANY_VERSION) == 0) {
+ String message = "Version " + metaInfo.versionCode
+ + " > installed version " + packageInfo.versionCode;
+ Slog.w(TAG, "Package " + packageName + ": " + message);
+ EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
+ packageName, message);
+ executeNextState(RestoreState.RUNNING_QUEUE);
+ return;
+ } else {
+ if (DEBUG) Slog.v(TAG, "Version " + metaInfo.versionCode
+ + " > installed " + packageInfo.versionCode
+ + " but restoreAnyVersion");
+ }
+ }
- // done; we can finally release the wakelock
- mWakelock.release();
+ if (!signaturesMatch(metaInfo.signatures, packageInfo)) {
+ Slog.w(TAG, "Signature mismatch restoring " + packageName);
+ EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
+ "Signature mismatch");
+ executeNextState(RestoreState.RUNNING_QUEUE);
+ return;
+ }
+
+ if (DEBUG) Slog.v(TAG, "Package " + packageName
+ + " restore version [" + metaInfo.versionCode
+ + "] is compatible with installed version ["
+ + packageInfo.versionCode + "]");
+
+ // Then set up and bind the agent
+ IBackupAgent agent = bindToAgentSynchronous(
+ packageInfo.applicationInfo,
+ IApplicationThread.BACKUP_MODE_INCREMENTAL);
+ if (agent == null) {
+ Slog.w(TAG, "Can't find backup agent for " + packageName);
+ EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
+ "Restore agent missing");
+ executeNextState(RestoreState.RUNNING_QUEUE);
+ return;
+ }
+
+ // And then finally start the restore on this agent
+ try {
+ initiateOneRestore(packageInfo, metaInfo.versionCode, agent, mNeedFullBackup);
+ ++mCount;
+ } catch (Exception e) {
+ Slog.e(TAG, "Error when attempting restore: " + e.toString());
+ agentErrorCleanup();
+ executeNextState(RestoreState.RUNNING_QUEUE);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to fetch restore data from transport");
+ mStatus = BackupConstants.TRANSPORT_ERROR;
+ executeNextState(RestoreState.FINAL);
}
}
- // Do the guts of a restore of one application, using mTransport.getRestoreData().
- void processOneRestore(PackageInfo app, int appVersionCode, IBackupAgent agent,
+ void finalizeRestore() {
+ if (MORE_DEBUG) Slog.d(TAG, "finishing restore mObserver=" + mObserver);
+
+ try {
+ mTransport.finishRestore();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error finishing restore", e);
+ }
+
+ if (mObserver != null) {
+ try {
+ mObserver.restoreFinished(mStatus);
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Restore observer died at restoreFinished");
+ }
+ }
+
+ // If this was a restoreAll operation, record that this was our
+ // ancestral dataset, as well as the set of apps that are possibly
+ // restoreable from the dataset
+ if (mTargetPackage == null && mPmAgent != null) {
+ mAncestralPackages = mPmAgent.getRestoredPackages();
+ mAncestralToken = mToken;
+ writeRestoreTokens();
+ }
+
+ // We must under all circumstances tell the Package Manager to
+ // proceed with install notifications if it's waiting for us.
+ if (mPmToken > 0) {
+ if (MORE_DEBUG) Slog.v(TAG, "finishing PM token " + mPmToken);
+ try {
+ mPackageManagerBinder.finishPackageInstall(mPmToken);
+ } catch (RemoteException e) { /* can't happen */ }
+ }
+
+ // Furthermore we need to reset the session timeout clock
+ mBackupHandler.removeMessages(MSG_RESTORE_TIMEOUT);
+ mBackupHandler.sendEmptyMessageDelayed(MSG_RESTORE_TIMEOUT,
+ TIMEOUT_RESTORE_INTERVAL);
+
+ // done; we can finally release the wakelock
+ Slog.i(TAG, "Restore complete.");
+ mWakelock.release();
+ }
+
+ // Call asynchronously into the app, passing it the restore data. The next step
+ // after this is always a callback, either operationComplete() or handleTimeout().
+ void initiateOneRestore(PackageInfo app, int appVersionCode, IBackupAgent agent,
boolean needFullBackup) {
- // !!! TODO: actually run the restore through mTransport
+ mCurrentPackage = app;
final String packageName = app.packageName;
- if (DEBUG) Slog.d(TAG, "processOneRestore packageName=" + packageName);
+ if (DEBUG) Slog.d(TAG, "initiateOneRestore packageName=" + packageName);
// !!! TODO: get the dirs from the transport
- File backupDataName = new File(mDataDir, packageName + ".restore");
- File newStateName = new File(mStateDir, packageName + ".new");
- File savedStateName = new File(mStateDir, packageName);
-
- ParcelFileDescriptor backupData = null;
- ParcelFileDescriptor newState = null;
+ mBackupDataName = new File(mDataDir, packageName + ".restore");
+ mNewStateName = new File(mStateDir, packageName + ".new");
+ mSavedStateName = new File(mStateDir, packageName);
final int token = generateToken();
try {
// Run the transport's restore pass
- backupData = ParcelFileDescriptor.open(backupDataName,
+ mBackupData = ParcelFileDescriptor.open(mBackupDataName,
ParcelFileDescriptor.MODE_READ_WRITE |
ParcelFileDescriptor.MODE_CREATE |
ParcelFileDescriptor.MODE_TRUNCATE);
- if (mTransport.getRestoreData(backupData) != BackupConstants.TRANSPORT_OK) {
+ if (mTransport.getRestoreData(mBackupData) != BackupConstants.TRANSPORT_OK) {
Slog.e(TAG, "Error getting restore data for " + packageName);
EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
return;
}
// Okay, we have the data. Now have the agent do the restore.
- backupData.close();
- backupData = ParcelFileDescriptor.open(backupDataName,
+ mBackupData.close();
+ mBackupData = ParcelFileDescriptor.open(mBackupDataName,
ParcelFileDescriptor.MODE_READ_ONLY);
- newState = ParcelFileDescriptor.open(newStateName,
+ mNewState = ParcelFileDescriptor.open(mNewStateName,
ParcelFileDescriptor.MODE_READ_WRITE |
ParcelFileDescriptor.MODE_CREATE |
ParcelFileDescriptor.MODE_TRUNCATE);
// Kick off the restore, checking for hung agents
- prepareOperationTimeout(token, TIMEOUT_RESTORE_INTERVAL);
- agent.doRestore(backupData, appVersionCode, newState, token, mBackupManagerBinder);
- boolean success = waitUntilOperationComplete(token);
-
- if (!success) {
- throw new RuntimeException("restore timeout");
- }
-
- // if everything went okay, remember the recorded state now
- //
- // !!! TODO: the restored data should be migrated on the server
- // side into the current dataset. In that case the new state file
- // we just created would reflect the data already extant in the
- // backend, so there'd be nothing more to do. Until that happens,
- // however, we need to make sure that we record the data to the
- // current backend dataset. (Yes, this means shipping the data over
- // the wire in both directions. That's bad, but consistency comes
- // first, then efficiency.) Once we introduce server-side data
- // migration to the newly-restored device's dataset, we will change
- // the following from a discard of the newly-written state to the
- // "correct" operation of renaming into the canonical state blob.
- newStateName.delete(); // TODO: remove; see above comment
- //newStateName.renameTo(savedStateName); // TODO: replace with this
-
- int size = (int) backupDataName.length();
- EventLog.writeEvent(EventLogTags.RESTORE_PACKAGE, packageName, size);
+ prepareOperationTimeout(token, TIMEOUT_RESTORE_INTERVAL, this);
+ agent.doRestore(mBackupData, appVersionCode, mNewState, token, mBackupManagerBinder);
} catch (Exception e) {
- Slog.e(TAG, "Error restoring data for " + packageName, e);
+ Slog.e(TAG, "Unable to call app for restore: " + packageName, e);
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName, e.toString());
+ agentErrorCleanup(); // clears any pending timeout messages as well
- // If the agent fails restore, it might have put the app's data
- // into an incoherent state. For consistency we wipe its data
- // again in this case before propagating the exception
- clearApplicationDataSynchronous(packageName);
- } finally {
- backupDataName.delete();
- try { if (backupData != null) backupData.close(); } catch (IOException e) {}
- try { if (newState != null) newState.close(); } catch (IOException e) {}
- backupData = newState = null;
- synchronized (mCurrentOperations) {
- mCurrentOperations.delete(token);
- }
+ // After a restore failure we go back to running the queue. If there
+ // are no more packages to be restored that will be handled by the
+ // next step.
+ executeNextState(RestoreState.RUNNING_QUEUE);
+ }
+ }
- // If we know a priori that we'll need to perform a full post-restore backup
- // pass, clear the new state file data. This means we're discarding work that
- // was just done by the app's agent, but this way the agent doesn't need to
- // take any special action based on global device state.
- if (needFullBackup) {
- newStateName.delete();
+ void agentErrorCleanup() {
+ // If the agent fails restore, it might have put the app's data
+ // into an incoherent state. For consistency we wipe its data
+ // again in this case before continuing with normal teardown
+ clearApplicationDataSynchronous(mCurrentPackage.packageName);
+ agentCleanup();
+ }
+
+ void agentCleanup() {
+ mBackupDataName.delete();
+ try { if (mBackupData != null) mBackupData.close(); } catch (IOException e) {}
+ try { if (mNewState != null) mNewState.close(); } catch (IOException e) {}
+ mBackupData = mNewState = null;
+
+ // if everything went okay, remember the recorded state now
+ //
+ // !!! TODO: the restored data should be migrated on the server
+ // side into the current dataset. In that case the new state file
+ // we just created would reflect the data already extant in the
+ // backend, so there'd be nothing more to do. Until that happens,
+ // however, we need to make sure that we record the data to the
+ // current backend dataset. (Yes, this means shipping the data over
+ // the wire in both directions. That's bad, but consistency comes
+ // first, then efficiency.) Once we introduce server-side data
+ // migration to the newly-restored device's dataset, we will change
+ // the following from a discard of the newly-written state to the
+ // "correct" operation of renaming into the canonical state blob.
+ mNewStateName.delete(); // TODO: remove; see above comment
+ //mNewStateName.renameTo(mSavedStateName); // TODO: replace with this
+
+ // If this wasn't the PM pseudopackage, tear down the agent side
+ if (mCurrentPackage.applicationInfo != null) {
+ // unbind and tidy up even on timeout or failure
+ try {
+ mActivityManager.unbindBackupAgent(mCurrentPackage.applicationInfo);
+
+ // The agent was probably running with a stub Application object,
+ // which isn't a valid run mode for the main app logic. Shut
+ // down the app so that next time it's launched, it gets the
+ // usual full initialization. Note that this is only done for
+ // full-system restores: when a single app has requested a restore,
+ // it is explicitly not killed following that operation.
+ if (mTargetPackage == null && (mCurrentPackage.applicationInfo.flags
+ & ApplicationInfo.FLAG_KILL_AFTER_RESTORE) != 0) {
+ if (DEBUG) Slog.d(TAG, "Restore complete, killing host process of "
+ + mCurrentPackage.applicationInfo.processName);
+ mActivityManager.killApplicationProcess(
+ mCurrentPackage.applicationInfo.processName,
+ mCurrentPackage.applicationInfo.uid);
+ }
+ } catch (RemoteException e) {
+ // can't happen; we run in the same process as the activity manager
}
}
+
+ // The caller is responsible for reestablishing the state machine; our
+ // responsibility here is to clear the decks for whatever comes next.
+ mBackupHandler.removeMessages(MSG_TIMEOUT, this);
+ synchronized (mCurrentOpLock) {
+ mCurrentOperations.clear();
+ }
+ }
+
+ // A call to agent.doRestore() has been positively acknowledged as complete
+ @Override
+ public void operationComplete() {
+ int size = (int) mBackupDataName.length();
+ EventLog.writeEvent(EventLogTags.RESTORE_PACKAGE, mCurrentPackage.packageName, size);
+ // Just go back to running the restore queue
+ agentCleanup();
+
+ executeNextState(RestoreState.RUNNING_QUEUE);
+ }
+
+ // A call to agent.doRestore() has timed out
+ @Override
+ public void handleTimeout() {
+ Slog.e(TAG, "Timeout restoring application " + mCurrentPackage.packageName);
+ EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
+ mCurrentPackage.packageName, "restore timeout");
+ // Handle like an agent that threw on invocation: wipe it and go on to the next
+ agentErrorCleanup();
+ executeNextState(RestoreState.RUNNING_QUEUE);
+ }
+
+ void executeNextState(RestoreState nextState) {
+ if (MORE_DEBUG) Slog.i(TAG, " => executing next step on "
+ + this + " nextState=" + nextState);
+ mCurrentState = nextState;
+ Message msg = mBackupHandler.obtainMessage(MSG_BACKUP_RESTORE_STEP, this);
+ mBackupHandler.sendMessage(msg);
}
}
@@ -4884,12 +5279,23 @@
// Note that a currently-active backup agent has notified us that it has
// completed the given outstanding asynchronous backup/restore operation.
+ @Override
public void opComplete(int token) {
+ if (MORE_DEBUG) Slog.v(TAG, "opComplete: " + Integer.toHexString(token));
+ Operation op = null;
synchronized (mCurrentOpLock) {
- if (MORE_DEBUG) Slog.v(TAG, "opComplete: " + Integer.toHexString(token));
- mCurrentOperations.put(token, OP_ACKNOWLEDGED);
+ op = mCurrentOperations.get(token);
+ if (op != null) {
+ op.state = OP_ACKNOWLEDGED;
+ }
mCurrentOpLock.notifyAll();
}
+
+ // The completion callback, if any, is invoked on the handler
+ if (op != null && op.callback != null) {
+ Message msg = mBackupHandler.obtainMessage(MSG_OP_COMPLETE, op.callback);
+ mBackupHandler.sendMessage(msg);
+ }
}
// ----- Restore session -----
diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java
index a80a2b8..e6b5898 100644
--- a/services/java/com/android/server/WifiService.java
+++ b/services/java/com/android/server/WifiService.java
@@ -37,7 +37,7 @@
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiWatchdogStateMachine;
import android.net.wifi.WifiConfiguration.KeyMgmt;
-import android.net.wifi.WpsConfiguration;
+import android.net.wifi.Wps;
import android.net.wifi.WpsResult;
import android.net.ConnectivityManager;
import android.net.DhcpInfo;
@@ -286,7 +286,7 @@
}
case WifiManager.CMD_START_WPS: {
//replyTo has the original source
- mWifiStateMachine.startWps(msg.replyTo, (WpsConfiguration)msg.obj);
+ mWifiStateMachine.startWps(msg.replyTo, (Wps)msg.obj);
break;
}
case WifiManager.CMD_DISABLE_NETWORK: {
diff --git a/test-runner/src/android/test/InstrumentationCoreTestRunner.java b/test-runner/src/android/test/InstrumentationCoreTestRunner.java
index ff99a74..036a2275 100644
--- a/test-runner/src/android/test/InstrumentationCoreTestRunner.java
+++ b/test-runner/src/android/test/InstrumentationCoreTestRunner.java
@@ -51,35 +51,33 @@
/**
* Convenience definition of our log tag.
- */
+ */
private static final String TAG = "InstrumentationCoreTestRunner";
-
+
/**
* True if (and only if) we are running in single-test mode (as opposed to
* batch mode).
*/
private boolean singleTest = false;
-
+
@Override
public void onCreate(Bundle arguments) {
// We might want to move this to /sdcard, if is is mounted/writable.
File cacheDir = getTargetContext().getCacheDir();
- // Set some properties that the core tests absolutely need.
+ // Set some properties that the core tests absolutely need.
System.setProperty("user.language", "en");
System.setProperty("user.region", "US");
-
+
System.setProperty("java.home", cacheDir.getAbsolutePath());
System.setProperty("user.home", cacheDir.getAbsolutePath());
System.setProperty("java.io.tmpdir", cacheDir.getAbsolutePath());
- System.setProperty("javax.net.ssl.trustStore",
- "/etc/security/cacerts.bks");
-
+
if (arguments != null) {
String classArg = arguments.getString(ARGUMENT_TEST_CLASS);
- singleTest = classArg != null && classArg.contains("#");
+ singleTest = classArg != null && classArg.contains("#");
}
-
+
super.onCreate(arguments);
}
@@ -89,36 +87,36 @@
runner.addTestListener(new TestListener() {
/**
- * The last test class we executed code from.
+ * The last test class we executed code from.
*/
private Class<?> lastClass;
-
+
/**
* The minimum time we expect a test to take.
*/
private static final int MINIMUM_TIME = 100;
-
+
/**
* The start time of our current test in System.currentTimeMillis().
*/
private long startTime;
-
+
public void startTest(Test test) {
if (test.getClass() != lastClass) {
lastClass = test.getClass();
printMemory(test.getClass());
}
-
+
Thread.currentThread().setContextClassLoader(
test.getClass().getClassLoader());
-
+
startTime = System.currentTimeMillis();
}
-
+
public void endTest(Test test) {
if (test instanceof TestCase) {
cleanup((TestCase)test);
-
+
/*
* Make sure all tests take at least MINIMUM_TIME to
* complete. If they don't, we wait a bit. The Cupcake
@@ -126,7 +124,7 @@
* short time, which causes headache for the CTS.
*/
long timeTaken = System.currentTimeMillis() - startTime;
-
+
if (timeTaken < MINIMUM_TIME) {
try {
Thread.sleep(MINIMUM_TIME - timeTaken);
@@ -136,15 +134,15 @@
}
}
}
-
+
public void addError(Test test, Throwable t) {
// This space intentionally left blank.
}
-
+
public void addFailure(Test test, AssertionFailedError t) {
// This space intentionally left blank.
}
-
+
/**
* Dumps some memory info.
*/
@@ -154,7 +152,7 @@
long total = runtime.totalMemory();
long free = runtime.freeMemory();
long used = total - free;
-
+
Log.d(TAG, "Total memory : " + total);
Log.d(TAG, "Used memory : " + used);
Log.d(TAG, "Free memory : " + free);
@@ -170,7 +168,7 @@
*/
private void cleanup(TestCase test) {
Class<?> clazz = test.getClass();
-
+
while (clazz != TestCase.class) {
Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
@@ -185,15 +183,15 @@
}
}
}
-
+
clazz = clazz.getSuperclass();
}
}
-
+
});
-
+
return runner;
- }
+ }
@Override
List<Predicate<TestMethod>> getBuilderRequirements() {
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 0757efd..27a60cd 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -18,7 +18,7 @@
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WpsConfiguration;
+import android.net.wifi.Wps;
import android.net.wifi.WpsResult;
import android.net.wifi.ScanResult;
import android.net.DhcpInfo;
diff --git a/wifi/java/android/net/wifi/WifiConfigStore.java b/wifi/java/android/net/wifi/WifiConfigStore.java
index 83dda5c..c75dec7 100644
--- a/wifi/java/android/net/wifi/WifiConfigStore.java
+++ b/wifi/java/android/net/wifi/WifiConfigStore.java
@@ -397,7 +397,7 @@
* Start WPS pin method configuration with pin obtained
* from the access point
*/
- static WpsResult startWpsWithPinFromAccessPoint(WpsConfiguration config) {
+ static WpsResult startWpsWithPinFromAccessPoint(Wps config) {
WpsResult result = new WpsResult();
if (WifiNative.startWpsWithPinFromAccessPointCommand(config.BSSID, config.pin)) {
/* WPS leaves all networks disabled */
@@ -415,7 +415,7 @@
* from the device
* @return WpsResult indicating status and pin
*/
- static WpsResult startWpsWithPinFromDevice(WpsConfiguration config) {
+ static WpsResult startWpsWithPinFromDevice(Wps config) {
WpsResult result = new WpsResult();
result.pin = WifiNative.startWpsWithPinFromDeviceCommand(config.BSSID);
/* WPS leaves all networks disabled */
@@ -432,7 +432,7 @@
/**
* Start WPS push button configuration
*/
- static WpsResult startWpsPbc(WpsConfiguration config) {
+ static WpsResult startWpsPbc(Wps config) {
WpsResult result = new WpsResult();
if (WifiNative.startWpsPbcCommand(config.BSSID)) {
/* WPS leaves all networks disabled */
@@ -594,7 +594,7 @@
sendConfiguredNetworksChangedBroadcast();
}
- static void updateIpAndProxyFromWpsConfig(int netId, WpsConfiguration wpsConfig) {
+ static void updateIpAndProxyFromWpsConfig(int netId, Wps wpsConfig) {
synchronized (sConfiguredNetworks) {
WifiConfiguration config = sConfiguredNetworks.get(netId);
if (config != null) {
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 5f8385c..0fce8e8 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1175,7 +1175,7 @@
* @param config WPS configuration
* @hide
*/
- public void startWps(WpsConfiguration config) {
+ public void startWps(Wps config) {
if (config == null) {
return;
}
diff --git a/wifi/java/android/net/wifi/WifiNative.java b/wifi/java/android/net/wifi/WifiNative.java
index cebcc47..6cc09e9 100644
--- a/wifi/java/android/net/wifi/WifiNative.java
+++ b/wifi/java/android/net/wifi/WifiNative.java
@@ -261,10 +261,10 @@
public static String p2pConnect(WifiP2pConfig config, boolean joinExistingGroup) {
if (config == null) return null;
List<String> args = new ArrayList<String>();
- WpsConfiguration wpsConfig = config.wpsConfig;
+ Wps wps = config.wps;
args.add(config.deviceAddress);
- switch (wpsConfig.setup) {
+ switch (wps.setup) {
case PBC:
args.add("pbc");
break;
@@ -274,11 +274,11 @@
args.add("display");
break;
case KEYPAD:
- args.add(wpsConfig.pin);
+ args.add(wps.pin);
args.add("keypad");
break;
case LABEL:
- args.add(wpsConfig.pin);
+ args.add(wps.pin);
args.add("label");
default:
break;
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index d116e5b..175a9ce 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -880,7 +880,7 @@
sendMessage(message);
}
- public void startWps(Messenger replyTo, WpsConfiguration config) {
+ public void startWps(Messenger replyTo, Wps config) {
Message msg = obtainMessage(CMD_START_WPS, config);
msg.replyTo = replyTo;
sendMessage(msg);
diff --git a/wifi/java/android/net/wifi/WpsConfiguration.aidl b/wifi/java/android/net/wifi/Wps.aidl
similarity index 95%
rename from wifi/java/android/net/wifi/WpsConfiguration.aidl
rename to wifi/java/android/net/wifi/Wps.aidl
index 6c26833..ba82a9a 100644
--- a/wifi/java/android/net/wifi/WpsConfiguration.aidl
+++ b/wifi/java/android/net/wifi/Wps.aidl
@@ -16,4 +16,4 @@
package android.net.wifi;
-parcelable WpsConfiguration;
+parcelable Wps;
diff --git a/wifi/java/android/net/wifi/WpsConfiguration.java b/wifi/java/android/net/wifi/Wps.java
similarity index 85%
rename from wifi/java/android/net/wifi/WpsConfiguration.java
rename to wifi/java/android/net/wifi/Wps.java
index 0c2adfd..6d006960 100644
--- a/wifi/java/android/net/wifi/WpsConfiguration.java
+++ b/wifi/java/android/net/wifi/Wps.java
@@ -25,12 +25,14 @@
import java.util.BitSet;
/**
- * A class representing a WPS network configuration
+ * A class representing Wi-Fi Protected Setup
* @hide
+ *
+ * {@see WifiP2pConfig}
*/
-public class WpsConfiguration implements Parcelable {
+public class Wps implements Parcelable {
- /* Wi-Fi Protected Setup. www.wi-fi.org/wifi-protected-setup has details */
+ /** Wi-Fi Protected Setup. www.wi-fi.org/wifi-protected-setup has details */
public enum Setup {
/* Push button configuration */
PBC,
@@ -49,6 +51,7 @@
/** @hide */
public String BSSID;
+ /** Passed with pin method configuration */
public String pin;
/** @hide */
@@ -60,8 +63,7 @@
/** @hide */
public LinkProperties linkProperties;
- /** @hide */
- public WpsConfiguration() {
+ public Wps() {
setup = Setup.INVALID;
BSSID = null;
pin = null;
@@ -94,7 +96,7 @@
}
/** copy constructor {@hide} */
- public WpsConfiguration(WpsConfiguration source) {
+ public Wps(Wps source) {
if (source != null) {
setup = source.setup;
BSSID = source.BSSID;
@@ -116,10 +118,10 @@
}
/** Implement the Parcelable interface {@hide} */
- public static final Creator<WpsConfiguration> CREATOR =
- new Creator<WpsConfiguration>() {
- public WpsConfiguration createFromParcel(Parcel in) {
- WpsConfiguration config = new WpsConfiguration();
+ public static final Creator<Wps> CREATOR =
+ new Creator<Wps>() {
+ public Wps createFromParcel(Parcel in) {
+ Wps config = new Wps();
config.setup = Setup.valueOf(in.readString());
config.BSSID = in.readString();
config.pin = in.readString();
@@ -129,8 +131,8 @@
return config;
}
- public WpsConfiguration[] newArray(int size) {
- return new WpsConfiguration[size];
+ public Wps[] newArray(int size) {
+ return new Wps[size];
}
};
}
diff --git a/wifi/java/android/net/wifi/WpsStateMachine.java b/wifi/java/android/net/wifi/WpsStateMachine.java
index af089ab..f9e903a 100644
--- a/wifi/java/android/net/wifi/WpsStateMachine.java
+++ b/wifi/java/android/net/wifi/WpsStateMachine.java
@@ -53,7 +53,7 @@
private WifiStateMachine mWifiStateMachine;
- private WpsConfiguration mWpsConfig;
+ private Wps mWpsConfig;
private Context mContext;
AsyncChannel mReplyChannel = new AsyncChannel();
@@ -90,10 +90,10 @@
@Override
public boolean processMessage(Message message) {
if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
- WpsConfiguration wpsConfig;
+ Wps wpsConfig;
switch (message.what) {
case WifiStateMachine.CMD_START_WPS:
- mWpsConfig = (WpsConfiguration) message.obj;
+ mWpsConfig = (Wps) message.obj;
WpsResult result;
switch (mWpsConfig.setup) {
case PBC:
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
index 686d698..e359ce5 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
@@ -16,26 +16,28 @@
package android.net.wifi.p2p;
-import android.net.wifi.WpsConfiguration;
-import android.net.wifi.WpsConfiguration.Setup;
+import android.net.wifi.Wps;
+import android.net.wifi.Wps.Setup;
import android.os.Parcelable;
import android.os.Parcel;
/**
- * A class representing a Wi-Fi P2p configuration
+ * A class representing a Wi-Fi P2p configuration for setting up a connection
* @hide
+ *
+ * {@see WifiP2pManager}
*/
public class WifiP2pConfig implements Parcelable {
/**
- * Device address
+ * The device MAC address uniquely identifies a Wi-Fi p2p device
*/
public String deviceAddress;
/**
- * WPS configuration
+ * Wi-Fi Protected Setup information
*/
- public WpsConfiguration wpsConfig;
+ public Wps wps;
/**
* This is an integer value between 0 and 15 where 0 indicates the least
@@ -61,11 +63,11 @@
public WifiP2pConfig() {
//set defaults
- wpsConfig = new WpsConfiguration();
- wpsConfig.setup = Setup.PBC;
+ wps = new Wps();
+ wps.setup = Setup.PBC;
}
- /* P2P-GO-NEG-REQUEST 42:fc:89:a8:96:09 dev_passwd_id=4 */
+ /** P2P-GO-NEG-REQUEST 42:fc:89:a8:96:09 dev_passwd_id=4 {@hide}*/
public WifiP2pConfig(String supplicantEvent) throws IllegalArgumentException {
String[] tokens = supplicantEvent.split(" ");
@@ -74,7 +76,7 @@
}
deviceAddress = tokens[1];
- wpsConfig = new WpsConfiguration();
+ wps = new Wps();
if (tokens.length > 2) {
String[] nameVal = tokens[2].split("=");
@@ -87,28 +89,29 @@
//As defined in wps/wps_defs.h
switch (devPasswdId) {
case 0x00:
- wpsConfig.setup = Setup.LABEL;
+ wps.setup = Setup.LABEL;
break;
case 0x01:
- wpsConfig.setup = Setup.KEYPAD;
+ wps.setup = Setup.KEYPAD;
break;
case 0x04:
- wpsConfig.setup = Setup.PBC;
+ wps.setup = Setup.PBC;
break;
case 0x05:
- wpsConfig.setup = Setup.DISPLAY;
+ wps.setup = Setup.DISPLAY;
break;
default:
- wpsConfig.setup = Setup.PBC;
+ wps.setup = Setup.PBC;
break;
}
}
}
+ /** @hide */
public String toString() {
StringBuffer sbuf = new StringBuffer();
sbuf.append("\n address: ").append(deviceAddress);
- sbuf.append("\n wps: ").append(wpsConfig);
+ sbuf.append("\n wps: ").append(wps);
sbuf.append("\n groupOwnerIntent: ").append(groupOwnerIntent);
sbuf.append("\n persist: ").append(persist.toString());
return sbuf.toString();
@@ -129,7 +132,7 @@
/** Implement the Parcelable interface {@hide} */
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(deviceAddress);
- dest.writeParcelable(wpsConfig, flags);
+ dest.writeParcelable(wps, flags);
dest.writeInt(groupOwnerIntent);
dest.writeString(persist.name());
}
@@ -140,7 +143,7 @@
public WifiP2pConfig createFromParcel(Parcel in) {
WifiP2pConfig config = new WifiP2pConfig();
config.deviceAddress = in.readString();
- config.wpsConfig = (WpsConfiguration) in.readParcelable(null);
+ config.wps = (Wps) in.readParcelable(null);
config.groupOwnerIntent = in.readInt();
config.persist = Persist.valueOf(in.readString());
return config;
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java b/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java
index 7908726..99c585f 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java
@@ -25,17 +25,20 @@
/**
* A class representing a Wi-Fi p2p device
* @hide
+ *
+ * {@see WifiP2pManager}
*/
public class WifiP2pDevice implements Parcelable {
private static final String TAG = "WifiP2pDevice";
+
/**
- * Device name
+ * The device name is a user friendly string to identify a Wi-Fi p2p device
*/
public String deviceName;
/**
- * Device MAC address
+ * The device MAC address uniquely identifies a Wi-Fi p2p device
*/
public String deviceAddress;
@@ -46,35 +49,30 @@
* P2P Interface Address and the group interface will be created with
* address as the local address in case of successfully completed
* negotiation.
+ * @hide
*/
public String interfaceAddress;
/**
- * Primary device type
+ * Primary device type identifies the type of device. For example, an application
+ * could filter the devices discovered to only display printers if the purpose is to
+ * enable a printing action from the user. See the Wi-Fi Direct technical specification
+ * for the full list of standard device types supported.
*/
public String primaryDeviceType;
/**
- * Secondary device type
+ * Secondary device type is an optional attribute that can be provided by a device in
+ * addition to the primary device type.
*/
public String secondaryDeviceType;
// These definitions match the ones in wpa_supplicant
/* WPS config methods supported */
- private static final int WPS_CONFIG_USBA = 0x0001;
- private static final int WPS_CONFIG_ETHERNET = 0x0002;
- private static final int WPS_CONFIG_LABEL = 0x0004;
private static final int WPS_CONFIG_DISPLAY = 0x0008;
- private static final int WPS_CONFIG_EXT_NFC_TOKEN = 0x0010;
- private static final int WPS_CONFIG_INT_NFC_TOKEN = 0x0020;
- private static final int WPS_CONFIG_NFC_INTERFACE = 0x0040;
private static final int WPS_CONFIG_PUSHBUTTON = 0x0080;
private static final int WPS_CONFIG_KEYPAD = 0x0100;
- private static final int WPS_CONFIG_VIRT_PUSHBUTTON = 0x0280;
- private static final int WPS_CONFIG_PHY_PUSHBUTTON = 0x0480;
- private static final int WPS_CONFIG_VIRT_DISPLAY = 0x2008;
- private static final int WPS_CONFIG_PHY_DISPLAY = 0x4008;
/* Device Capability bitmap */
private static final int DEVICE_CAPAB_SERVICE_DISCOVERY = 1;
@@ -95,19 +93,23 @@
/**
* WPS config methods supported
+ * @hide
*/
public int wpsConfigMethodsSupported;
/**
* Device capability
+ * @hide
*/
public int deviceCapability;
/**
* Group capability
+ * @hide
*/
public int groupCapability;
+ /** Device connection status */
public enum Status {
CONNECTED,
INVITED,
@@ -118,7 +120,7 @@
public Status status = Status.UNAVAILABLE;
- public WifiP2pDevice() {
+ WifiP2pDevice() {
}
/**
@@ -144,6 +146,7 @@
* group_capab=0x0
*
* Note: The events formats can be looked up in the wpa_supplicant code
+ * @hide
*/
public WifiP2pDevice(String string) throws IllegalArgumentException {
String[] tokens = string.split(" ");
@@ -198,11 +201,33 @@
}
}
+ /** Returns true if WPS push button configuration is supported */
+ public boolean wpsPbcSupported() {
+ return (wpsConfigMethodsSupported & WPS_CONFIG_PUSHBUTTON) != 0;
+ }
+
+ /** Returns true if WPS keypad configuration is supported */
+ public boolean wpsKeypadSupported() {
+ return (wpsConfigMethodsSupported & WPS_CONFIG_KEYPAD) != 0;
+ }
+
+ /** Returns true if WPS display configuration is supported */
+ public boolean wpsDisplaySupported() {
+ return (wpsConfigMethodsSupported & WPS_CONFIG_DISPLAY) != 0;
+ }
+
+ /** Returns true if the device is capable of service discovery */
+ public boolean isServiceDiscoveryCapable() {
+ return (deviceCapability & DEVICE_CAPAB_SERVICE_DISCOVERY) != 0;
+ }
+
+ /** Returns true if the device is a group owner */
public boolean isGroupOwner() {
return (groupCapability & GROUP_CAPAB_GROUP_OWNER) != 0;
}
@Override
+ /** @hide */
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof WifiP2pDevice)) return false;
@@ -214,6 +239,7 @@
return other.deviceAddress.equals(deviceAddress);
}
+ /** @hide */
public String toString() {
StringBuffer sbuf = new StringBuffer();
sbuf.append("Device: ").append(deviceName);
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java b/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java
index aa3554c..242bce0 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java
@@ -28,22 +28,25 @@
/**
* A class representing a Wi-Fi P2p device list
* @hide
+ *
+ * {@see WifiP2pManager}
*/
public class WifiP2pDeviceList implements Parcelable {
private Collection<WifiP2pDevice> mDevices;
- public WifiP2pDeviceList() {
+ WifiP2pDeviceList() {
mDevices = new ArrayList<WifiP2pDevice>();
}
- //copy constructor
+ /** copy constructor {@hide} */
public WifiP2pDeviceList(WifiP2pDeviceList source) {
if (source != null) {
mDevices = source.getDeviceList();
}
}
+ /** @hide */
public WifiP2pDeviceList(ArrayList<WifiP2pDevice> devices) {
mDevices = new ArrayList<WifiP2pDevice>();
for (WifiP2pDevice device : devices) {
@@ -51,12 +54,14 @@
}
}
+ /** @hide */
public boolean clear() {
if (mDevices.isEmpty()) return false;
mDevices.clear();
return true;
}
+ /** @hide */
public void update(WifiP2pDevice device) {
if (device == null) return;
for (WifiP2pDevice d : mDevices) {
@@ -75,15 +80,18 @@
mDevices.add(device);
}
+ /** @hide */
public boolean remove(WifiP2pDevice device) {
if (device == null) return false;
return mDevices.remove(device);
}
+ /** Get the list of devices */
public Collection<WifiP2pDevice> getDeviceList() {
return Collections.unmodifiableCollection(mDevices);
}
+ /** @hide */
public String toString() {
StringBuffer sbuf = new StringBuffer();
for (WifiP2pDevice device : mDevices) {
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java b/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java
index e35d360..48f210b 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java
@@ -27,6 +27,8 @@
/**
* A class representing a Wi-Fi P2p group
* @hide
+ *
+ * {@see WifiP2pManager}
*/
public class WifiP2pGroup implements Parcelable {
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pInfo.java b/wifi/java/android/net/wifi/p2p/WifiP2pInfo.java
index a02175e..81b7708 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pInfo.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pInfo.java
@@ -23,15 +23,20 @@
import java.net.UnknownHostException;
/**
- * A class representing connection info on Wi-fi P2p
+ * A class representing connection information about a Wi-Fi p2p group
* @hide
+ *
+ * {@see WifiP2pManager}
*/
public class WifiP2pInfo implements Parcelable {
+ /** Indicates if a p2p group has been successfully formed */
public boolean groupFormed;
+ /** Indicates if the current device is the group owner */
public boolean isGroupOwner;
+ /** Group owner address */
public InetAddress groupOwnerAddress;
/** @hide */
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
index 11de9c4..10a316e 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
@@ -64,7 +64,7 @@
* use {@link #requestConnectionInfo} to fetch the connection details. Connection information
* can be obtained with {@link #connectionInfoInResponse} on a {@link #RESPONSE_CONNECTION_INFO}
* message. The connection info {@link WifiP2pInfo} contains the address of the group owner
- * {@link WifiP2pInfo#groupOwnerAddress} and a flag {@link #WifiP2pInfo#isGroupOwner} to indicate
+ * {@link WifiP2pInfo#groupOwnerAddress} and a flag {@link WifiP2pInfo#isGroupOwner} to indicate
* if the current device is a p2p group owner. A p2p client can thus communicate with
* the p2p group owner through a socket connection.
*
@@ -85,6 +85,7 @@
* {@see WifiP2pGroup}
* {@see WifiP2pDevice}
* {@see WifiP2pDeviceList}
+ * {@see android.net.wifi.Wps}
* @hide
*/
public class WifiP2pManager {
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pService.java b/wifi/java/android/net/wifi/p2p/WifiP2pService.java
index 5297302..361cac5 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pService.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pService.java
@@ -41,8 +41,8 @@
import android.net.wifi.WifiMonitor;
import android.net.wifi.WifiNative;
import android.net.wifi.WifiStateMachine;
-import android.net.wifi.WpsConfiguration;
-import android.net.wifi.WpsConfiguration.Setup;
+import android.net.wifi.Wps;
+import android.net.wifi.Wps.Setup;
import android.net.wifi.p2p.WifiP2pDevice.Status;
import android.os.Binder;
import android.os.IBinder;
@@ -128,7 +128,9 @@
public static final int GROUP_NEGOTIATION_TIMED_OUT = BASE + 3;
/* User accepted to disable Wi-Fi in order to enable p2p */
- private static final int WIFI_DISABLE_USER_ACCEPT = BASE + 11;
+ private static final int WIFI_DISABLE_USER_ACCEPT = BASE + 4;
+ /* User rejected to disable Wi-Fi in order to enable p2p */
+ private static final int WIFI_DISABLE_USER_REJECT = BASE + 5;
private final boolean mP2pSupported;
private final String mDeviceType;
@@ -359,6 +361,7 @@
break;
// Ignore
case WIFI_DISABLE_USER_ACCEPT:
+ case WIFI_DISABLE_USER_REJECT:
case GROUP_NEGOTIATION_TIMED_OUT:
break;
default:
@@ -457,8 +460,7 @@
if (which == DialogInterface.BUTTON_POSITIVE) {
sendMessage(WIFI_DISABLE_USER_ACCEPT);
} else {
- logd("User rejected enabling p2p");
- //ignore
+ sendMessage(WIFI_DISABLE_USER_REJECT);
}
}
};
@@ -509,6 +511,11 @@
mWifiChannel.sendMessage(P2P_ENABLE_PENDING);
transitionTo(mWaitForWifiDisableState);
break;
+ case WIFI_DISABLE_USER_REJECT:
+ logd("User rejected enabling p2p");
+ sendP2pStateChangedBroadcast(false);
+ transitionTo(mP2pDisabledState);
+ break;
case WifiP2pManager.ENABLE_P2P:
case WifiP2pManager.DISABLE_P2P:
deferMessage(message);
@@ -1027,7 +1034,7 @@
private void notifyP2pGoNegotationRequest(WifiP2pConfig config) {
Resources r = Resources.getSystem();
- WpsConfiguration wpsConfig = config.wpsConfig;
+ Wps wps = config.wps;
final View textEntryView = LayoutInflater.from(mContext)
.inflate(R.layout.wifi_p2p_go_negotiation_request_alert, null);
final EditText pin = (EditText) textEntryView .findViewById(R.id.wifi_p2p_wps_pin);
@@ -1040,10 +1047,10 @@
if (DBG) logd(getName() + " connect " + pin.getText());
if (pin.getVisibility() == View.GONE) {
- mSavedGoNegotiationConfig.wpsConfig.setup = Setup.PBC;
+ mSavedGoNegotiationConfig.wps.setup = Setup.PBC;
} else {
- mSavedGoNegotiationConfig.wpsConfig.setup = Setup.KEYPAD;
- mSavedGoNegotiationConfig.wpsConfig.pin = pin.getText().toString();
+ mSavedGoNegotiationConfig.wps.setup = Setup.KEYPAD;
+ mSavedGoNegotiationConfig.wps.pin = pin.getText().toString();
}
sendMessage(WifiP2pManager.CONNECT, mSavedGoNegotiationConfig);
mSavedGoNegotiationConfig = null;
@@ -1058,7 +1065,7 @@
})
.create();
- if (wpsConfig.setup == Setup.PBC) {
+ if (wps.setup == Setup.PBC) {
pin.setVisibility(View.GONE);
dialog.setMessage(r.getString(R.string.wifi_p2p_pbc_go_negotiation_request_message,
config.deviceAddress));