Merge "Fix for the notification panel getting stuck open."
diff --git a/api/current.xml b/api/current.xml
index 86023fb..f915fad 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -3265,6 +3265,17 @@
visibility="public"
>
</field>
+<field name="customTokens"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843593"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="cycles"
type="int"
transient="false"
@@ -18308,6 +18319,28 @@
visibility="public"
>
</field>
+<field name="KEY_CALLER_PID"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""callerPid""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="KEY_CALLER_UID"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""callerUid""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="KEY_ERROR_CODE"
type="java.lang.String"
transient="false"
@@ -18555,6 +18588,28 @@
</parameter>
<parameter name="prefId" type="int">
</parameter>
+<parameter name="customTokens" type="boolean">
+</parameter>
+</constructor>
+<constructor name="AuthenticatorDescription"
+ type="android.accounts.AuthenticatorDescription"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="type" type="java.lang.String">
+</parameter>
+<parameter name="packageName" type="java.lang.String">
+</parameter>
+<parameter name="labelId" type="int">
+</parameter>
+<parameter name="iconId" type="int">
+</parameter>
+<parameter name="smallIconId" type="int">
+</parameter>
+<parameter name="prefId" type="int">
+</parameter>
</constructor>
<method name="describeContents"
return="int"
@@ -18615,6 +18670,16 @@
visibility="public"
>
</field>
+<field name="customTokens"
+ type="boolean"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="iconId"
type="int"
transient="false"
@@ -223231,7 +223296,7 @@
abstract="false"
static="false"
final="true"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
<constructor name="CacheManager"
@@ -223249,7 +223314,7 @@
synchronized="false"
static="true"
final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
</method>
@@ -223271,7 +223336,7 @@
synchronized="false"
static="true"
final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
<parameter name="url" type="java.lang.String">
@@ -223286,7 +223351,7 @@
synchronized="false"
static="true"
final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
</method>
@@ -223297,7 +223362,7 @@
synchronized="false"
static="true"
final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
<parameter name="url" type="java.lang.String">
@@ -223322,7 +223387,7 @@
abstract="false"
static="true"
final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
<constructor name="CacheManager.CacheResult"
diff --git a/core/java/android/accounts/AccountAuthenticatorCache.java b/core/java/android/accounts/AccountAuthenticatorCache.java
index 524d3f4..7214c50 100644
--- a/core/java/android/accounts/AccountAuthenticatorCache.java
+++ b/core/java/android/accounts/AccountAuthenticatorCache.java
@@ -38,7 +38,7 @@
* @hide
*/
/* package private */ class AccountAuthenticatorCache
- extends RegisteredServicesCache<AuthenticatorDescription>
+ extends RegisteredServicesCache<AuthenticatorDescription>
implements IAccountAuthenticatorCache {
private static final String TAG = "Account";
private static final MySerializer sSerializer = new MySerializer();
@@ -64,11 +64,13 @@
com.android.internal.R.styleable.AccountAuthenticator_smallIcon, 0);
final int prefId = sa.getResourceId(
com.android.internal.R.styleable.AccountAuthenticator_accountPreferences, 0);
+ final boolean customTokens = sa.getBoolean(
+ com.android.internal.R.styleable.AccountAuthenticator_customTokens, false);
if (TextUtils.isEmpty(accountType)) {
return null;
}
- return new AuthenticatorDescription(accountType, packageName, labelId, iconId,
- smallIconId, prefId);
+ return new AuthenticatorDescription(accountType, packageName, labelId, iconId,
+ smallIconId, prefId, customTokens);
} finally {
sa.recycle();
}
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index fd3a0d0..6388dc5 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -188,6 +188,12 @@
public static final String KEY_ERROR_CODE = "errorCode";
public static final String KEY_ERROR_MESSAGE = "errorMessage";
public static final String KEY_USERDATA = "userdata";
+ /**
+ * Authenticators using 'customTokens' option will also get the UID of the
+ * caller
+ */
+ public static final String KEY_CALLER_UID = "callerUid";
+ public static final String KEY_CALLER_PID = "callerPid";
public static final String ACTION_AUTHENTICATOR_INTENT =
"android.accounts.AccountAuthenticator";
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
index a815b3a..f19b58b 100644
--- a/core/java/android/accounts/AccountManagerService.java
+++ b/core/java/android/accounts/AccountManagerService.java
@@ -893,13 +893,29 @@
if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
checkBinderPermission(Manifest.permission.USE_CREDENTIALS);
final int callerUid = Binder.getCallingUid();
- final boolean permissionGranted = permissionIsGranted(account, authTokenType, callerUid);
+ final int callerPid = Binder.getCallingPid();
+
+ AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo =
+ mAuthenticatorCache.getServiceInfo(
+ AuthenticatorDescription.newKey(account.type));
+ final boolean customTokens =
+ authenticatorInfo != null && authenticatorInfo.type.customTokens;
+
+ // skip the check if customTokens
+ final boolean permissionGranted = customTokens ||
+ permissionIsGranted(account, authTokenType, callerUid);
+
+ if (customTokens) {
+ // let authenticator know the identity of the caller
+ loginOptions.putInt(AccountManager.KEY_CALLER_UID, callerUid);
+ loginOptions.putInt(AccountManager.KEY_CALLER_PID, callerPid);
+ }
long identityToken = clearCallingIdentity();
try {
// if the caller has permission, do the peek. otherwise go the more expensive
// route of starting a Session
- if (permissionGranted) {
+ if (!customTokens && permissionGranted) {
String authToken = readAuthTokenFromCache(account, authTokenType);
if (authToken != null) {
Bundle result = new Bundle();
@@ -953,8 +969,10 @@
"the type and name should not be empty");
return;
}
- saveAuthTokenToDatabase(new Account(name, type),
- authTokenType, authToken);
+ if (!customTokens) {
+ saveAuthTokenToDatabase(new Account(name, type),
+ authTokenType, authToken);
+ }
}
Intent intent = result.getParcelable(AccountManager.KEY_INTENT);
diff --git a/core/java/android/accounts/AuthenticatorDescription.java b/core/java/android/accounts/AuthenticatorDescription.java
index c6515672..5d9abb0 100644
--- a/core/java/android/accounts/AuthenticatorDescription.java
+++ b/core/java/android/accounts/AuthenticatorDescription.java
@@ -44,9 +44,12 @@
/** The package name that can be used to lookup the resources from above. */
final public String packageName;
+ /** Authenticator handles its own token caching and permission screen */
+ final public boolean customTokens;
+
/** A constructor for a full AuthenticatorDescription */
public AuthenticatorDescription(String type, String packageName, int labelId, int iconId,
- int smallIconId, int prefId) {
+ int smallIconId, int prefId, boolean customTokens) {
if (type == null) throw new IllegalArgumentException("type cannot be null");
if (packageName == null) throw new IllegalArgumentException("packageName cannot be null");
this.type = type;
@@ -55,6 +58,12 @@
this.iconId = iconId;
this.smallIconId = smallIconId;
this.accountPreferencesId = prefId;
+ this.customTokens = customTokens;
+ }
+
+ public AuthenticatorDescription(String type, String packageName, int labelId, int iconId,
+ int smallIconId, int prefId) {
+ this(type, packageName, labelId, iconId, smallIconId, prefId, false);
}
/**
@@ -74,6 +83,7 @@
this.iconId = 0;
this.smallIconId = 0;
this.accountPreferencesId = 0;
+ this.customTokens = false;
}
private AuthenticatorDescription(Parcel source) {
@@ -83,6 +93,7 @@
this.iconId = source.readInt();
this.smallIconId = source.readInt();
this.accountPreferencesId = source.readInt();
+ this.customTokens = source.readByte() == 1;
}
/** @inheritDoc */
@@ -115,6 +126,7 @@
dest.writeInt(iconId);
dest.writeInt(smallIconId);
dest.writeInt(accountPreferencesId);
+ dest.writeByte((byte) (customTokens ? 1 : 0));
}
/** Used to create the object from a parcel. */
diff --git a/core/java/android/content/SyncActivityTooManyDeletes.java b/core/java/android/content/SyncActivityTooManyDeletes.java
new file mode 100644
index 0000000..350f35e
--- /dev/null
+++ b/core/java/android/content/SyncActivityTooManyDeletes.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import com.android.internal.R;
+import android.accounts.Account;
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.LinearLayout;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+/**
+ * Presents multiple options for handling the case where a sync was aborted because there
+ * were too many pending deletes. One option is to force the delete, another is to rollback
+ * the deletes, the third is to do nothing.
+ * @hide
+ */
+public class SyncActivityTooManyDeletes extends Activity
+ implements AdapterView.OnItemClickListener {
+
+ private long mNumDeletes;
+ private Account mAccount;
+ private String mAuthority;
+ private String mProvider;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Bundle extras = getIntent().getExtras();
+ if (extras == null) {
+ finish();
+ return;
+ }
+
+ mNumDeletes = extras.getLong("numDeletes");
+ mAccount = (Account) extras.getParcelable("account");
+ mAuthority = extras.getString("authority");
+ mProvider = extras.getString("provider");
+
+ // the order of these must match up with the constants for position used in onItemClick
+ CharSequence[] options = new CharSequence[]{
+ getResources().getText(R.string.sync_really_delete),
+ getResources().getText(R.string.sync_undo_deletes),
+ getResources().getText(R.string.sync_do_nothing)
+ };
+
+ ListAdapter adapter = new ArrayAdapter<CharSequence>(this,
+ android.R.layout.simple_list_item_1,
+ android.R.id.text1,
+ options);
+
+ ListView listView = new ListView(this);
+ listView.setAdapter(adapter);
+ listView.setItemsCanFocus(true);
+ listView.setOnItemClickListener(this);
+
+ TextView textView = new TextView(this);
+ CharSequence tooManyDeletesDescFormat =
+ getResources().getText(R.string.sync_too_many_deletes_desc);
+ textView.setText(String.format(tooManyDeletesDescFormat.toString(),
+ mNumDeletes, mProvider, mAccount.name));
+
+ final LinearLayout ll = new LinearLayout(this);
+ ll.setOrientation(LinearLayout.VERTICAL);
+ final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, 0);
+ ll.addView(textView, lp);
+ ll.addView(listView, lp);
+
+ // TODO: consider displaying the icon of the account type
+// AuthenticatorDescription[] descs = AccountManager.get(this).getAuthenticatorTypes();
+// for (AuthenticatorDescription desc : descs) {
+// if (desc.type.equals(mAccount.type)) {
+// try {
+// final Context authContext = createPackageContext(desc.packageName, 0);
+// ImageView imageView = new ImageView(this);
+// imageView.setImageDrawable(authContext.getResources().getDrawable(desc.iconId));
+// ll.addView(imageView, lp);
+// } catch (PackageManager.NameNotFoundException e) {
+// }
+// break;
+// }
+// }
+
+ setContentView(ll);
+ }
+
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ // the constants for position correspond to the items options array in onCreate()
+ if (position == 0) startSyncReallyDelete();
+ else if (position == 1) startSyncUndoDeletes();
+ finish();
+ }
+
+ private void startSyncReallyDelete() {
+ Bundle extras = new Bundle();
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS, true);
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true);
+ ContentResolver.requestSync(mAccount, mAuthority, extras);
+ }
+
+ private void startSyncUndoDeletes() {
+ Bundle extras = new Bundle();
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS, true);
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true);
+ ContentResolver.requestSync(mAccount, mAuthority, extras);
+ }
+}
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index 599429b..8b292c9 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -2157,9 +2157,7 @@
}
CharSequence authorityName = providerInfo.loadLabel(mContext.getPackageManager());
- Intent clickIntent = new Intent();
- clickIntent.setClassName("com.android.providers.subscribedfeeds",
- "com.android.settings.SyncActivityTooManyDeletes");
+ Intent clickIntent = new Intent(mContext, SyncActivityTooManyDeletes.class);
clickIntent.putExtra("account", account);
clickIntent.putExtra("authority", authority);
clickIntent.putExtra("provider", authorityName.toString());
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index d27e99d..75aebc9 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -8206,8 +8206,9 @@
/**
* Manually render this view (and all of its children) to the given Canvas.
* The view must have already done a full layout before this function is
- * called. When implementing a view, do not override this method; instead,
- * you should implement {@link #onDraw}.
+ * called. When implementing a view, implement {@link #onDraw} instead of
+ * overriding this method. If you do need to override this method, call
+ * the superclass version.
*
* @param canvas The Canvas to which the View is rendered.
*/
diff --git a/core/java/android/webkit/CacheManager.java b/core/java/android/webkit/CacheManager.java
index fd0a6d0..99be64b 100644
--- a/core/java/android/webkit/CacheManager.java
+++ b/core/java/android/webkit/CacheManager.java
@@ -44,7 +44,10 @@
* this component and if they can not be resolved by the cache, the HTTP headers
* are attached, as appropriate, to the request for revalidation of content. The
* class also manages the cache size.
+ *
+ * @deprecated Access to the HTTP cache will be removed in a future release.
*/
+@Deprecated
public final class CacheManager {
private static final String LOGTAG = "cache";
@@ -85,7 +88,10 @@
* This class represents a resource retrieved from the HTTP cache.
* Instances of this class can be obtained by invoking the
* CacheManager.getCacheFile() method.
+ *
+ * @deprecated Access to the HTTP cache will be removed in a future release.
*/
+ @Deprecated
public static class CacheResult {
// these fields are saved to the database
int httpStatusCode;
@@ -220,9 +226,12 @@
/**
* get the base directory of the cache. With localPath of the CacheResult,
* it identifies the cache file.
- *
+ *
* @return File The base directory of the cache.
+ *
+ * @deprecated Access to the HTTP cache will be removed in a future release.
*/
+ @Deprecated
public static File getCacheFileBaseDir() {
return mBaseDir;
}
@@ -244,9 +253,12 @@
/**
* get the state of the current cache, enabled or disabled
- *
+ *
* @return return if it is disabled
+ *
+ * @deprecated Access to the HTTP cache will be removed in a future release.
*/
+ @Deprecated
public static boolean cacheDisabled() {
return mDisabled;
}
@@ -314,8 +326,11 @@
* HEADER_KEY_IFNONEMATCH or HEADER_KEY_IFMODIFIEDSINCE will be set in the
* cached headers.
*
- * @return the CacheResult for a given url
+ * @return the CacheResult for a given url.
+ *
+ * @deprecated Access to the HTTP cache will be removed in a future release.
*/
+ @Deprecated
public static CacheResult getCacheFile(String url,
Map<String, String> headers) {
return getCacheFile(url, 0, headers);
@@ -390,7 +405,10 @@
* @return CacheResult for a given url
* @hide - hide createCacheFile since it has a parameter of type headers, which is
* in a hidden package.
+ *
+ * @deprecated Access to the HTTP cache will be removed in a future release.
*/
+ @Deprecated
public static CacheResult createCacheFile(String url, int statusCode,
Headers headers, String mimeType, boolean forceCache) {
return createCacheFile(url, statusCode, headers, mimeType, 0,
@@ -455,7 +473,10 @@
/**
* Save the info of a cache file for a given url to the CacheMap so that it
* can be reused later
+ *
+ * @deprecated Access to the HTTP cache will be removed in a future release.
*/
+ @Deprecated
public static void saveCacheFile(String url, CacheResult cacheRet) {
saveCacheFile(url, 0, cacheRet);
}
diff --git a/core/java/android/webkit/SelectActionModeCallback.java b/core/java/android/webkit/SelectActionModeCallback.java
index 54c9d9a..86a67c7 100644
--- a/core/java/android/webkit/SelectActionModeCallback.java
+++ b/core/java/android/webkit/SelectActionModeCallback.java
@@ -34,7 +34,11 @@
}
void finish() {
- mActionMode.finish();
+ // It is possible that onCreateActionMode was never called, in the case
+ // where there is no ActionBar, for example.
+ if (mActionMode != null) {
+ mActionMode.finish();
+ }
}
// ActionMode.Callback implementation
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 7195f98..05bb19d 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -3790,7 +3790,9 @@
public boolean selectText() {
int x = viewToContentX((int) mLastTouchX + mScrollX);
int y = viewToContentY((int) mLastTouchY + mScrollY);
- setUpSelect();
+ if (!setUpSelect()) {
+ return false;
+ }
if (mNativeClass != 0 && nativeWordSelection(x, y)) {
nativeSetExtendSelection();
mDrawSelectionPointer = false;
@@ -4675,10 +4677,15 @@
return false;
}
- private void setUpSelect() {
- if (0 == mNativeClass) return; // client isn't initialized
- if (inFullScreenMode()) return;
- if (mSelectingText) return;
+ /*
+ * Enter selecting text mode. Returns true if the WebView is now in
+ * selecting text mode (including if it was already in that mode, and this
+ * method did nothing).
+ */
+ private boolean setUpSelect() {
+ if (0 == mNativeClass) return false; // client isn't initialized
+ if (inFullScreenMode()) return false;
+ if (mSelectingText) return true;
mExtendSelection = false;
mSelectingText = mDrawSelectionPointer = true;
// don't let the picture change during text selection
@@ -4698,7 +4705,13 @@
nativeHideCursor();
mSelectCallback = new SelectActionModeCallback();
mSelectCallback.setWebView(this);
- startActionMode(mSelectCallback);
+ if (startActionMode(mSelectCallback) == null) {
+ // There is no ActionMode, so do not allow the user to modify a
+ // selection.
+ selectionDone();
+ return false;
+ }
+ return true;
}
/**
@@ -4715,7 +4728,9 @@
void selectAll() {
if (0 == mNativeClass) return; // client isn't initialized
if (inFullScreenMode()) return;
- if (!mSelectingText) setUpSelect();
+ if (!mSelectingText && !setUpSelect()) {
+ return;
+ }
nativeSelectAll();
mDrawSelectionPointer = false;
mExtendSelection = true;
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index b8c8913..fdd0710 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -1991,6 +1991,13 @@
.obtainMessage(WebCoreThread.RESUME_PRIORITY));
}
+ static void sendStaticMessage(int messageType, Object argument) {
+ if (sWebCoreHandler == null)
+ return;
+
+ sWebCoreHandler.sendMessage(sWebCoreHandler.obtainMessage(messageType, argument));
+ }
+
static void pauseUpdatePicture(WebViewCore core) {
// Note: there is one possible failure mode. If pauseUpdatePicture() is
// called from UI thread while WEBKIT_DRAW is just pulled out of the
diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java
index 7b35b51..2d9a5f9 100644
--- a/core/java/android/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -979,10 +979,7 @@
// children
// after we have completed drawing ourselves.
- // Draw the selector wheel if needed
- if (mDrawSelectorWheel) {
- super.draw(canvas);
- }
+ super.draw(canvas);
// Draw our children if we are not showing the selector wheel of fading
// it out
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 9a2cc89..95a7222 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -864,7 +864,6 @@
mInputType = EditorInfo.TYPE_NULL;
mInput = null;
bufferType = BufferType.SPANNABLE;
- setFocusableInTouchMode(true);
// So that selection can be changed using arrow keys and touch is handled.
setMovementMethod(ArrowKeyMovementMethod.getInstance());
} else if (editable) {
@@ -4073,7 +4072,7 @@
*
* Use {@link #setTextIsSelectable(boolean)} or the
* {@link android.R.styleable#TextView_textIsSelectable} XML attribute to make this TextView
- * selectable (the text is not selectable by default).
+ * selectable (text is not selectable by default).
*
* Note that the content of an EditText is always selectable.
*
@@ -4088,10 +4087,9 @@
/**
* Sets whether or not (default) the content of this view is selectable by the user.
*
- * Note that this methods affect the {@link #setFocusableInTouchMode(boolean)},
- * {@link #setFocusable(boolean)}, {@link #setClickable(boolean)} and
- * {@link #setLongClickable(boolean)} states and you may want to restore these if they were
- * customized.
+ * Note that this methods affect the {@link #setFocusable(boolean)},
+ * {@link #setClickable(boolean)} and {@link #setLongClickable(boolean)} states and you may want
+ * to restore these if they were customized.
*
* See {@link #isTextSelectable} for details.
*
@@ -4102,7 +4100,6 @@
mTextIsSelectable = selectable;
- setFocusableInTouchMode(selectable);
setFocusable(selectable);
setClickable(selectable);
setLongClickable(selectable);
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 92b50c7..981661a 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1335,6 +1335,11 @@
android:exported="true">
</activity>
+ <activity android:name="android.content.SyncActivityTooManyDeletes"
+ android:theme="@android:style/Theme.Holo.Dialog"
+ android:label="@string/sync_too_many_deletes">
+ </activity>
+
<activity android:name="com.android.server.ShutdownActivity"
android:permission="android.permission.SHUTDOWN"
android:excludeFromRecents="true">
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 98c9270..873f539 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -48,6 +48,7 @@
theme does not set this value, meaning it is based on whether the
window is floating. -->
<attr name="backgroundDimEnabled" format="boolean" />
+
<!-- =========== -->
<!-- Text styles -->
<!-- =========== -->
@@ -261,7 +262,7 @@
<!-- Flag indicating whether this is a translucent window. -->
<attr name="windowIsTranslucent" format="boolean" />
<!-- Flag indicating that this window's background should be the
- user's current wallpaper. -->
+ user's current wallpaper. -->
<attr name="windowShowWallpaper" format="boolean" />
<!-- This Drawable is overlaid over the foreground of the Window's content area, usually
to place a shadow below the title. -->
@@ -4310,7 +4311,7 @@
If not supplied, then no activity will be launched. -->
<attr name="configure" format="string" />
<!-- A preview of what the AppWidget will look like after it's configured.
- If not supplied, the AppWidget's icon will be used. -->
+ If not supplied, the AppWidget's icon will be used. -->
<attr name="previewImage" format="reference" />
<!-- The view id of the AppWidget subview which should be auto-advanced.
by the widget's host. -->
@@ -4421,6 +4422,10 @@
<attr name="smallIcon" format="reference"/>
<!-- A preferences.xml file for authenticator-specific settings. -->
<attr name="accountPreferences" format="reference"/>
+ <!-- Account handles its own token storage and permissions.
+ Default to false
+ -->
+ <attr name="customTokens" format="boolean"/>
</declare-styleable>
<!-- =============================== -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 3a5b238..7e06c86 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1407,6 +1407,7 @@
<public type="attr" name="fastScrollPreviewBackgroundRight" />
<public type="attr" name="fastScrollTrackDrawable" />
<public type="attr" name="fastScrollOverlayPosition" />
+ <public type="attr" name="customTokens" />
<public type="anim" name="animator_fade_in" />
<public type="anim" name="animator_fade_out" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e48321c..92f3593 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -298,12 +298,12 @@
<string name="shutdown_confirm_question">Would you like to shut down?</string>
<!-- Recent Tasks dialog: title
- TODO: this should move to SystemUI.apk, but the code for the old
+ TODO: this should move to SystemUI.apk, but the code for the old
recent dialog is still in the framework
-->
<string name="recent_tasks_title">Recent</string>
<!-- Recent Tasks dialog: message when there are no recent applications
- TODO: this should move to SystemUI.apk, but the code for the old
+ TODO: this should move to SystemUI.apk, but the code for the old
recent dialog is still in the framework
-->
<string name="no_recent_tasks">No recent applications.</string>
@@ -1363,17 +1363,17 @@
<!-- Title of policy access to limiting the user's password choices -->
<string name="policylab_limitPassword">Set password rules</string>
<!-- Description of policy access to limiting the user's password choices -->
- <string name="policydesc_limitPassword">Control the length and the characters
+ <string name="policydesc_limitPassword">Control the length and the characters
allowed in screen-unlock passwords</string>
<!-- Title of policy access to watch user login attempts -->
<string name="policylab_watchLogin">Monitor screen-unlock attempts</string>
<!-- Description of policy access to watch user login attempts -->
- <string name="policydesc_watchLogin" product="tablet">Monitor the number of incorrect passwords
- entered when unlocking the screen, and lock the tablet or erase all the tablet\'s
+ <string name="policydesc_watchLogin" product="tablet">Monitor the number of incorrect passwords
+ entered when unlocking the screen, and lock the tablet or erase all the tablet\'s
data if too many incorrect passwords are entered</string>
<!-- Description of policy access to watch user login attempts -->
- <string name="policydesc_watchLogin" product="default">Monitor the number of incorrect passwords
- entered when unlocking the screen, and lock the phone or erase all the phone\'s
+ <string name="policydesc_watchLogin" product="default">Monitor the number of incorrect passwords
+ entered when unlocking the screen, and lock the phone or erase all the phone\'s
data if too many incorrect passwords are entered</string>
<!-- Title of policy access to reset user's password -->
<string name="policylab_resetPassword">Change the screen-unlock password</string>
@@ -1386,10 +1386,10 @@
<!-- Title of policy access to wipe the user's data -->
<string name="policylab_wipeData">Erase all data</string>
<!-- Description of policy access to wipe the user's data -->
- <string name="policydesc_wipeData" product="tablet">Erase the tablet\'s data without warning,
+ <string name="policydesc_wipeData" product="tablet">Erase the tablet\'s data without warning,
by performing a factory data reset</string>
<!-- Description of policy access to wipe the user's data -->
- <string name="policydesc_wipeData" product="default">Erase the phone\'s data without warning,
+ <string name="policydesc_wipeData" product="default">Erase the phone\'s data without warning,
by performing a factory data reset</string>
<string name="policylab_setGlobalProxy">Set the device global proxy</string>
<!-- Description of policy access to wipe the user's data -->
@@ -1602,7 +1602,7 @@
<string name="relationTypeSister">Sister</string>
<!-- Spouse relationship type [CHAR LIMIT=20] -->
<string name="relationTypeSpouse">Spouse</string>
-
+
<!-- Custom SIP address type -->
<string name="sipAddressTypeCustom">Custom</string>
<!-- Home SIP address type -->
@@ -2640,4 +2640,15 @@
<!-- Network positioning verification No. Button to push to deny sharing of location
information. -->
<string name="gpsVerifNo">No</string>
+
+ <!-- Error message when the sync tried to delete too many things -->
+ <string name="sync_too_many_deletes">Delete limit exceeded</string>
+ <!-- Dialog message for when there are too many deletes that would take place and we want user confirmation -->
+ <string name="sync_too_many_deletes_desc">There are <xliff:g id="number_of_deleted_items">%1$d</xliff:g> deleted items for <xliff:g id="type_of_sync">%2$s</xliff:g>, account <xliff:g id="account_name">%3$s</xliff:g>. What would you like to do?</string>
+ <!-- Dialog action for when there are too many deletes that would take place and we want user confirmation, and the user wants to delete the items -->
+ <string name="sync_really_delete">Delete the items.</string>
+ <!-- Dialog action for when there are too many deletes that would take place and we want user confirmation, and the user wants to undo the deletions -->
+ <string name="sync_undo_deletes">Undo the deletes.</string>
+ <!-- Dialog action for when there are too many deletes that would take place and we want user confirmation, and the user wants to do nothing for now -->
+ <string name="sync_do_nothing">Do nothing for now.</string>
</resources>
diff --git a/core/tests/coretests/src/android/net/http/DefaultHttpClientTest.java b/core/tests/coretests/src/android/net/http/DefaultHttpClientTest.java
new file mode 100644
index 0000000..ad3ec3d
--- /dev/null
+++ b/core/tests/coretests/src/android/net/http/DefaultHttpClientTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.http;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StringWriter;
+import junit.framework.TestCase;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.DefaultHttpClient;
+import tests.http.MockResponse;
+import tests.http.MockWebServer;
+import tests.http.SocketPolicy;
+import static tests.http.SocketPolicy.DISCONNECT_AT_END;
+import static tests.http.SocketPolicy.SHUTDOWN_INPUT_AT_END;
+import static tests.http.SocketPolicy.SHUTDOWN_OUTPUT_AT_END;
+
+public final class DefaultHttpClientTest extends TestCase {
+
+ private MockWebServer server = new MockWebServer();
+
+ @Override protected void tearDown() throws Exception {
+ server.shutdown();
+ super.tearDown();
+ }
+
+ public void testServerClosesSocket() throws Exception {
+ testServerClosesOutput(DISCONNECT_AT_END);
+ }
+
+ public void testServerShutdownInput() throws Exception {
+ testServerClosesOutput(SHUTDOWN_INPUT_AT_END);
+ }
+
+ /**
+ * DefaultHttpClient fails if the server shutdown the output after the
+ * response was sent. http://b/2612240
+ */
+ public void testServerShutdownOutput() throws Exception {
+ testServerClosesOutput(SHUTDOWN_OUTPUT_AT_END);
+ }
+
+ private void testServerClosesOutput(SocketPolicy socketPolicy) throws Exception {
+ server.enqueue(new MockResponse()
+ .setBody("This connection won't pool properly")
+ .setSocketPolicy(socketPolicy));
+ server.enqueue(new MockResponse()
+ .setBody("This comes after a busted connection"));
+ server.play();
+
+ DefaultHttpClient client = new DefaultHttpClient();
+
+ HttpResponse a = client.execute(new HttpGet(server.getUrl("/a").toURI()));
+ assertEquals("This connection won't pool properly", contentToString(a));
+ assertEquals(0, server.takeRequest().getSequenceNumber());
+
+ HttpResponse b = client.execute(new HttpGet(server.getUrl("/b").toURI()));
+ assertEquals("This comes after a busted connection", contentToString(b));
+ // sequence number 0 means the HTTP socket connection was not reused
+ assertEquals(0, server.takeRequest().getSequenceNumber());
+ }
+
+ private String contentToString(HttpResponse response) throws IOException {
+ StringWriter writer = new StringWriter();
+ char[] buffer = new char[1024];
+ Reader reader = new InputStreamReader(response.getEntity().getContent());
+ int length;
+ while ((length = reader.read(buffer)) != -1) {
+ writer.write(buffer, 0, length);
+ }
+ reader.close();
+ return writer.toString();
+ }
+}
diff --git a/include/media/mediascanner.h b/include/media/mediascanner.h
index 74c9d5d..df5be32 100644
--- a/include/media/mediascanner.h
+++ b/include/media/mediascanner.h
@@ -71,7 +71,8 @@
bool addStringTag(const char* name, const char* value);
void endFile();
- virtual bool scanFile(const char* path, long long lastModified, long long fileSize) = 0;
+ virtual bool scanFile(const char* path, long long lastModified,
+ long long fileSize, bool isDirectory) = 0;
virtual bool handleStringTag(const char* name, const char* value) = 0;
virtual bool setMimeType(const char* mimeType) = 0;
virtual bool addNoMediaFolder(const char* path) = 0;
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index 63ec6b2..365fd65 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -407,55 +407,60 @@
private long mFileSize;
private String mWriter;
- public FileCacheEntry beginFile(String path, String mimeType, long lastModified, long fileSize) {
-
- // special case certain file names
- // I use regionMatches() instead of substring() below
- // to avoid memory allocation
- int lastSlash = path.lastIndexOf('/');
- if (lastSlash >= 0 && lastSlash + 2 < path.length()) {
- // ignore those ._* files created by MacOS
- if (path.regionMatches(lastSlash + 1, "._", 0, 2)) {
- return null;
- }
-
- // ignore album art files created by Windows Media Player:
- // Folder.jpg, AlbumArtSmall.jpg, AlbumArt_{...}_Large.jpg and AlbumArt_{...}_Small.jpg
- if (path.regionMatches(true, path.length() - 4, ".jpg", 0, 4)) {
- if (path.regionMatches(true, lastSlash + 1, "AlbumArt_{", 0, 10) ||
- path.regionMatches(true, lastSlash + 1, "AlbumArt.", 0, 9)) {
- return null;
- }
- int length = path.length() - lastSlash - 1;
- if ((length == 17 && path.regionMatches(true, lastSlash + 1, "AlbumArtSmall", 0, 13)) ||
- (length == 10 && path.regionMatches(true, lastSlash + 1, "Folder", 0, 6))) {
- return null;
- }
- }
- }
-
+ public FileCacheEntry beginFile(String path, String mimeType, long lastModified,
+ long fileSize, boolean isDirectory) {
mMimeType = mimeType;
mFileType = 0;
mFileSize = fileSize;
- // try mimeType first, if it is specified
- if (mimeType != null) {
- mFileType = MediaFile.getFileTypeForMimeType(mimeType);
- }
+ if (!isDirectory) {
+ // special case certain file names
+ // I use regionMatches() instead of substring() below
+ // to avoid memory allocation
+ int lastSlash = path.lastIndexOf('/');
+ if (lastSlash >= 0 && lastSlash + 2 < path.length()) {
+ // ignore those ._* files created by MacOS
+ if (path.regionMatches(lastSlash + 1, "._", 0, 2)) {
+ return null;
+ }
- // if mimeType was not specified, compute file type based on file extension.
- if (mFileType == 0) {
- MediaFile.MediaFileType mediaFileType = MediaFile.getFileType(path);
- if (mediaFileType != null) {
- mFileType = mediaFileType.fileType;
- if (mMimeType == null) {
- mMimeType = mediaFileType.mimeType;
+ // ignore album art files created by Windows Media Player:
+ // Folder.jpg, AlbumArtSmall.jpg, AlbumArt_{...}_Large.jpg
+ // and AlbumArt_{...}_Small.jpg
+ if (path.regionMatches(true, path.length() - 4, ".jpg", 0, 4)) {
+ if (path.regionMatches(true, lastSlash + 1, "AlbumArt_{", 0, 10) ||
+ path.regionMatches(true, lastSlash + 1, "AlbumArt.", 0, 9)) {
+ return null;
+ }
+ int length = path.length() - lastSlash - 1;
+ if ((length == 17 && path.regionMatches(
+ true, lastSlash + 1, "AlbumArtSmall", 0, 13)) ||
+ (length == 10
+ && path.regionMatches(true, lastSlash + 1, "Folder", 0, 6))) {
+ return null;
+ }
}
}
- }
- if (isDrmEnabled() && MediaFile.isDrmFileType(mFileType)) {
- mFileType = getFileTypeFromDrm(path);
+ // try mimeType first, if it is specified
+ if (mimeType != null) {
+ mFileType = MediaFile.getFileTypeForMimeType(mimeType);
+ }
+
+ // if mimeType was not specified, compute file type based on file extension.
+ if (mFileType == 0) {
+ MediaFile.MediaFileType mediaFileType = MediaFile.getFileType(path);
+ if (mediaFileType != null) {
+ mFileType = mediaFileType.fileType;
+ if (mMimeType == null) {
+ mMimeType = mediaFileType.mimeType;
+ }
+ }
+ }
+
+ if (isDrmEnabled() && MediaFile.isDrmFileType(mFileType)) {
+ mFileType = getFileTypeFromDrm(path);
+ }
}
String key = path;
@@ -470,7 +475,9 @@
FileCacheEntry entry = mFileCache.get(key);
if (entry == null) {
Uri tableUri;
- if (MediaFile.isVideoFileType(mFileType)) {
+ if (isDirectory) {
+ tableUri = mFilesUri;
+ } else if (MediaFile.isVideoFileType(mFileType)) {
tableUri = mVideoUri;
} else if (MediaFile.isImageFileType(mFileType)) {
tableUri = mImagesUri;
@@ -479,7 +486,8 @@
} else {
tableUri = mFilesUri;
}
- entry = new FileCacheEntry(tableUri, 0, path, 0, 0);
+ entry = new FileCacheEntry(tableUri, 0, path, 0,
+ (isDirectory ? MtpConstants.FORMAT_ASSOCIATION : 0));
mFileCache.put(key, entry);
}
entry.mSeenInFileSystem = true;
@@ -514,22 +522,19 @@
return entry;
}
- public void scanFile(String path, long lastModified, long fileSize) {
+ public void scanFile(String path, long lastModified, long fileSize, boolean isDirectory) {
// This is the callback funtion from native codes.
// Log.v(TAG, "scanFile: "+path);
- doScanFile(path, null, lastModified, fileSize, false);
- }
-
- public void scanFile(String path, String mimeType, long lastModified, long fileSize) {
- doScanFile(path, mimeType, lastModified, fileSize, false);
+ doScanFile(path, null, lastModified, fileSize, isDirectory, false);
}
public Uri doScanFile(String path, String mimeType, long lastModified,
- long fileSize, boolean scanAlways) {
+ long fileSize, boolean isDirectory, boolean scanAlways) {
Uri result = null;
// long t1 = System.currentTimeMillis();
try {
- FileCacheEntry entry = beginFile(path, mimeType, lastModified, fileSize);
+ FileCacheEntry entry = beginFile(path, mimeType, lastModified,
+ fileSize, isDirectory);
// rescan for metadata if file was modified since last scan
if (entry != null && (entry.mLastModifiedChanged || scanAlways)) {
String lowpath = path.toLowerCase();
@@ -775,7 +780,11 @@
values.put(MediaStore.MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, mMtpObjectHandle);
}
if (tableUri == mFilesUri) {
- values.put(Files.FileColumns.FORMAT, MediaFile.getFormatCode(entry.mPath, mMimeType));
+ int format = entry.mFormat;
+ if (format == 0) {
+ format = MediaFile.getFormatCode(entry.mPath, mMimeType);
+ }
+ values.put(Files.FileColumns.FORMAT, format);
}
// new file, insert it
result = mMediaProvider.insert(tableUri, values);
@@ -1060,8 +1069,7 @@
boolean fileMissing = false;
if (!entry.mSeenInFileSystem && !MtpConstants.isAbstractObject(entry.mFormat)) {
- if (entry.mFormat != MtpConstants.FORMAT_ASSOCIATION &&
- inScanDirectory(path, directories)) {
+ if (inScanDirectory(path, directories)) {
// we didn't see this file in the scan directory.
fileMissing = true;
} else {
@@ -1180,7 +1188,7 @@
long lastModifiedSeconds = file.lastModified() / 1000;
// always scan the file, so we can return the content://media Uri for existing files
- return mClient.doScanFile(path, mimeType, lastModifiedSeconds, file.length(), true);
+ return mClient.doScanFile(path, mimeType, lastModifiedSeconds, file.length(),false, true);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e);
return null;
@@ -1227,7 +1235,8 @@
long lastModifiedSeconds = file.lastModified() / 1000;
// always scan the file, so we can return the content://media Uri for existing files
- mClient.doScanFile(path, mediaFileType.mimeType, lastModifiedSeconds, file.length(), true);
+ mClient.doScanFile(path, mediaFileType.mimeType, lastModifiedSeconds, file.length(),
+ (format == MtpConstants.FORMAT_ASSOCIATION), true);
}
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e);
diff --git a/media/java/android/media/MediaScannerClient.java b/media/java/android/media/MediaScannerClient.java
index 258c3b4..ac326ef 100644
--- a/media/java/android/media/MediaScannerClient.java
+++ b/media/java/android/media/MediaScannerClient.java
@@ -21,9 +21,7 @@
*/
public interface MediaScannerClient
{
- public void scanFile(String path, long lastModified, long fileSize);
-
- public void scanFile(String path, String mimeType, long lastModified, long fileSize);
+ public void scanFile(String path, long lastModified, long fileSize, boolean isDirectory);
public void addNoMediaFolder(String path);
diff --git a/media/jni/android_media_MediaScanner.cpp b/media/jni/android_media_MediaScanner.cpp
index fd0b233..a5176fa 100644
--- a/media/jni/android_media_MediaScanner.cpp
+++ b/media/jni/android_media_MediaScanner.cpp
@@ -62,7 +62,7 @@
}
else {
mScanFileMethodID = env->GetMethodID(mediaScannerClientInterface, "scanFile",
- "(Ljava/lang/String;JJ)V");
+ "(Ljava/lang/String;JJZ)V");
mHandleStringTagMethodID = env->GetMethodID(mediaScannerClientInterface, "handleStringTag",
"(Ljava/lang/String;Ljava/lang/String;)V");
mSetMimeTypeMethodID = env->GetMethodID(mediaScannerClientInterface, "setMimeType",
@@ -78,12 +78,14 @@
}
// returns true if it succeeded, false if an exception occured in the Java code
- virtual bool scanFile(const char* path, long long lastModified, long long fileSize)
+ virtual bool scanFile(const char* path, long long lastModified,
+ long long fileSize, bool isDirectory)
{
jstring pathStr;
if ((pathStr = mEnv->NewStringUTF(path)) == NULL) return false;
- mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize);
+ mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified,
+ fileSize, isDirectory);
mEnv->DeleteLocalRef(pathStr);
return (!mEnv->ExceptionCheck());
diff --git a/media/libmedia/MediaScanner.cpp b/media/libmedia/MediaScanner.cpp
index c31b622..5ec573e 100644
--- a/media/libmedia/MediaScanner.cpp
+++ b/media/libmedia/MediaScanner.cpp
@@ -84,6 +84,7 @@
// place to copy file or directory name
char* fileSpot = path + strlen(path);
struct dirent* entry;
+ struct stat statbuf;
// ignore directories that contain a ".nomedia" file
if (pathRemaining >= 8 /* strlen(".nomedia") */ ) {
@@ -125,7 +126,6 @@
// If the type is unknown, stat() the file instead.
// This is sometimes necessary when accessing NFS mounted filesystems, but
// could be needed in other cases well.
- struct stat statbuf;
if (stat(path, &statbuf) == 0) {
if (S_ISREG(statbuf.st_mode)) {
type = DT_REG;
@@ -142,8 +142,15 @@
// for example, the Mac ".Trashes" directory
if (name[0] == '.') continue;
+ // report the directory to the client
+ if (stat(path, &statbuf) == 0) {
+ client.scanFile(path, statbuf.st_mtime, 0, true);
+ }
+
+ // and now process its contents
strcat(fileSpot, "/");
- int err = doProcessDirectory(path, pathRemaining - nameLength - 1, client, exceptionCheck, exceptionEnv);
+ int err = doProcessDirectory(path, pathRemaining - nameLength - 1, client,
+ exceptionCheck, exceptionEnv);
if (err) {
// pass exceptions up - ignore other errors
if (exceptionCheck && exceptionCheck(exceptionEnv)) goto failure;
@@ -151,11 +158,8 @@
continue;
}
} else {
- struct stat statbuf;
stat(path, &statbuf);
- if (statbuf.st_size > 0) {
- client.scanFile(path, statbuf.st_mtime, statbuf.st_size);
- }
+ client.scanFile(path, statbuf.st_mtime, statbuf.st_size, false);
if (exceptionCheck && exceptionCheck(exceptionEnv)) goto failure;
}
}
diff --git a/services/audioflinger/A2dpAudioInterface.cpp b/services/audioflinger/A2dpAudioInterface.cpp
index aee01ab..d926cb1 100644
--- a/services/audioflinger/A2dpAudioInterface.cpp
+++ b/services/audioflinger/A2dpAudioInterface.cpp
@@ -260,6 +260,7 @@
if (pRate) *pRate = lRate;
mDevice = device;
+ mBufferDurationUs = ((bufferSize() * 1000 )/ frameSize() / sampleRate()) * 1000;
return NO_ERROR;
}
@@ -288,6 +289,7 @@
if (mStandby) {
acquire_wake_lock (PARTIAL_WAKE_LOCK, sA2dpWakeLock);
mStandby = false;
+ mLastWriteTime = systemTime();
}
status = init();
@@ -308,6 +310,15 @@
buffer = (char *)buffer + status;
}
+ // if A2DP sink runs abnormally fast, sleep a little so that audioflinger mixer thread
+ // does no spin and starve other threads.
+ // NOTE: It is likely that the A2DP headset is being disconnected
+ nsecs_t now = systemTime();
+ if ((uint32_t)ns2us(now - mLastWriteTime) < (mBufferDurationUs >> 2)) {
+ LOGV("A2DP sink runs too fast");
+ usleep(mBufferDurationUs - (uint32_t)ns2us(now - mLastWriteTime));
+ }
+ mLastWriteTime = now;
return bytes;
}
@@ -316,7 +327,7 @@
standby();
// Simulate audio output timing in case of error
- usleep(((bytes * 1000 )/ frameSize() / sampleRate()) * 1000);
+ usleep(mBufferDurationUs);
return status;
}
diff --git a/services/audioflinger/A2dpAudioInterface.h b/services/audioflinger/A2dpAudioInterface.h
index cef1926..dbe2c6a 100644
--- a/services/audioflinger/A2dpAudioInterface.h
+++ b/services/audioflinger/A2dpAudioInterface.h
@@ -117,6 +117,8 @@
uint32_t mDevice;
bool mClosing;
bool mSuspended;
+ nsecs_t mLastWriteTime;
+ uint32_t mBufferDurationUs;
};
friend class A2dpAudioStreamOut;