Merge "Speeding up Recent Apps"
diff --git a/api/14.txt b/api/14.txt
index 0aa94b2..45bb882 100644
--- a/api/14.txt
+++ b/api/14.txt
@@ -2072,7 +2072,7 @@
method public java.lang.String getUserData(android.accounts.Account, java.lang.String);
method public android.accounts.AccountManagerFuture<java.lang.Boolean> hasFeatures(android.accounts.Account, java.lang.String[], android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler);
method public void invalidateAuthToken(java.lang.String, java.lang.String);
- method public static android.content.Intent newChooseAccountIntent(android.accounts.Account, java.util.ArrayList<android.accounts.Account>, java.lang.String[], android.os.Bundle);
+ method public static android.content.Intent newChooseAccountIntent(android.accounts.Account, java.util.ArrayList<android.accounts.Account>, java.lang.String[], boolean, java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle);
method public java.lang.String peekAuthToken(android.accounts.Account, java.lang.String);
method public android.accounts.AccountManagerFuture<java.lang.Boolean> removeAccount(android.accounts.Account, android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler);
method public void removeOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener);
diff --git a/api/current.txt b/api/current.txt
index 0aa94b2..45bb882 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2072,7 +2072,7 @@
method public java.lang.String getUserData(android.accounts.Account, java.lang.String);
method public android.accounts.AccountManagerFuture<java.lang.Boolean> hasFeatures(android.accounts.Account, java.lang.String[], android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler);
method public void invalidateAuthToken(java.lang.String, java.lang.String);
- method public static android.content.Intent newChooseAccountIntent(android.accounts.Account, java.util.ArrayList<android.accounts.Account>, java.lang.String[], android.os.Bundle);
+ method public static android.content.Intent newChooseAccountIntent(android.accounts.Account, java.util.ArrayList<android.accounts.Account>, java.lang.String[], boolean, java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle);
method public java.lang.String peekAuthToken(android.accounts.Account, java.lang.String);
method public android.accounts.AccountManagerFuture<java.lang.Boolean> removeAccount(android.accounts.Account, android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler);
method public void removeOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener);
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 530ecf1..150880c 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -1782,7 +1782,8 @@
* <p>
* The most common case is to call this with one account type, e.g.:
* <p>
- * <pre> newChooseAccountsIntent(null, null, new String[]{"com.google"}, null);</pre>
+ * <pre> newChooseAccountsIntent(null, null, new String[]{"com.google"}, false, null,
+ * null, null, null);</pre>
* @param selectedAccount if specified, indicates that the {@link Account} is the currently
* selected one, according to the caller's definition of selected.
* @param allowableAccounts an optional {@link ArrayList} of accounts that are allowed to be
@@ -1790,22 +1791,44 @@
* @param allowableAccountTypes an optional string array of account types. These are used
* both to filter the shown accounts and to filter the list of account types that are shown
* when adding an account.
- * @param addAccountOptions This {@link Bundle} is passed as the addAccount options
- * @return an {@link Intent} that can be used to launch the ChooseAccount activity flow.
+ * @param alwaysPromptForAccount if set the account chooser screen is always shown, otherwise
+ * it is only shown when there is more than one account from which to choose
+ * @param descriptionOverrideText if non-null this string is used as the description in the
+ * accounts chooser screen rather than the default
+ * @param addAccountAuthTokenType this string is passed as the {@link #addAccount}
+ * authTokenType parameter
+ * @param addAccountRequiredFeatures this string array is passed as the {@link #addAccount}
+ * requiredFeatures parameter
+ * @param addAccountOptions This {@link Bundle} is passed as the {@link #addAccount} options
+ * parameter
+ * @return an {@link Intent} that can be used to launch the ChooseAccount activity flow.
*/
static public Intent newChooseAccountIntent(Account selectedAccount,
ArrayList<Account> allowableAccounts,
String[] allowableAccountTypes,
+ boolean alwaysPromptForAccount,
+ String descriptionOverrideText,
+ String addAccountAuthTokenType,
+ String[] addAccountRequiredFeatures,
Bundle addAccountOptions) {
Intent intent = new Intent();
intent.setClassName("android", "android.accounts.ChooseTypeAndAccountActivity");
intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ALLOWABLE_ACCOUNTS_ARRAYLIST,
allowableAccounts);
- intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ALLOWABLE_ACCOUNT_TYPES_ARRAYLIST,
- allowableAccountTypes != null ? Lists.newArrayList(allowableAccountTypes) : 0);
+ intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY,
+ allowableAccountTypes);
intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE,
addAccountOptions);
intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_SELECTED_ACCOUNT, selectedAccount);
+ intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ALWAYS_PROMPT_FOR_ACCOUNT,
+ alwaysPromptForAccount);
+ intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_DESCRIPTION_TEXT_OVERRIDE,
+ descriptionOverrideText);
+ intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING,
+ addAccountAuthTokenType);
+ intent.putExtra(
+ ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY,
+ addAccountRequiredFeatures);
return intent;
}
diff --git a/core/java/android/accounts/ChooseAccountTypeActivity.java b/core/java/android/accounts/ChooseAccountTypeActivity.java
index 836164c..f53e6f3 100644
--- a/core/java/android/accounts/ChooseAccountTypeActivity.java
+++ b/core/java/android/accounts/ChooseAccountTypeActivity.java
@@ -52,12 +52,12 @@
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(R.layout.choose_account);
+ setContentView(R.layout.choose_account_type);
// Read the validAccountTypes, if present, and add them to the setOfAllowableAccountTypes
Set<String> setOfAllowableAccountTypes = null;
ArrayList<String> validAccountTypes = getIntent().getStringArrayListExtra(
- ChooseTypeAndAccountActivity.EXTRA_ALLOWABLE_ACCOUNT_TYPES_ARRAYLIST);
+ ChooseTypeAndAccountActivity.EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY);
if (validAccountTypes != null) {
setOfAllowableAccountTypes = new HashSet<String>(validAccountTypes.size());
for (String type : validAccountTypes) {
@@ -138,10 +138,14 @@
protected void runAddAccountForAuthenticator(AuthInfo authInfo) {
Log.d(TAG, "selected account type " + authInfo.name);
- Bundle options = getIntent().getBundleExtra(
+ final Bundle options = getIntent().getBundleExtra(
ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE);
- AccountManager.get(this).addAccount(authInfo.desc.type, null, null, options,
- this, this, null);
+ final String[] requiredFeatures = getIntent().getStringArrayExtra(
+ ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY);
+ final String authTokenType = getIntent().getStringExtra(
+ ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING);
+ AccountManager.get(this).addAccount(authInfo.desc.type, authTokenType, requiredFeatures,
+ options, this, this, null /* Handler */);
}
public void run(final AccountManagerFuture<Bundle> accountManagerFuture) {
diff --git a/core/java/android/accounts/ChooseTypeAndAccountActivity.java b/core/java/android/accounts/ChooseTypeAndAccountActivity.java
index a903399..b4030b9 100644
--- a/core/java/android/accounts/ChooseTypeAndAccountActivity.java
+++ b/core/java/android/accounts/ChooseTypeAndAccountActivity.java
@@ -23,6 +23,7 @@
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Parcelable;
+import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -57,25 +58,68 @@
* that match the types in this list, if this parameter is supplied. This list is also
* used to filter the allowable account types if add account is selected.
*/
- public static final String EXTRA_ALLOWABLE_ACCOUNT_TYPES_ARRAYLIST = "allowableAccountTypes";
+ public static final String EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY = "allowableAccountTypes";
/**
- * This is passed as the options bundle in AccountManager.addAccount() if it is called.
+ * This is passed as the addAccountOptions parameter in AccountManager.addAccount()
+ * if it is called.
*/
public static final String EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE = "addAccountOptions";
/**
+ * This is passed as the requiredFeatures parameter in AccountManager.addAccount()
+ * if it is called.
+ */
+ public static final String EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY =
+ "addAccountRequiredFeatures";
+
+ /**
+ * This is passed as the authTokenType string in AccountManager.addAccount()
+ * if it is called.
+ */
+ public static final String EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING = "authTokenType";
+
+ /**
* If set then the specified account is already "selected".
*/
public static final String EXTRA_SELECTED_ACCOUNT = "selectedAccount";
+ /**
+ * If true then display the account selection list even if there is just
+ * one account to choose from. boolean.
+ */
+ public static final String EXTRA_ALWAYS_PROMPT_FOR_ACCOUNT =
+ "alwaysPromptForAccount";
+
+ /**
+ * If set then this string willb e used as the description rather than
+ * the default.
+ */
+ public static final String EXTRA_DESCRIPTION_TEXT_OVERRIDE =
+ "descriptionTextOverride";
+
private ArrayList<AccountInfo> mAccountInfos;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.choose_type_and_account);
+
+ // save some items we use frequently
final AccountManager accountManager = AccountManager.get(this);
+ final Intent intent = getIntent();
+
+ // override the description text if supplied
+ final String descriptionOverride =
+ intent.getStringExtra(EXTRA_DESCRIPTION_TEXT_OVERRIDE);
+ if (!TextUtils.isEmpty(descriptionOverride)) {
+ ((TextView)findViewById(R.id.description)).setText(descriptionOverride);
+ }
+
+ // If the selected account matches one in the list we will place a
+ // checkmark next to it.
+ final Account selectedAccount =
+ (Account)intent.getParcelableExtra(EXTRA_SELECTED_ACCOUNT);
// build an efficiently queryable map of account types to authenticator descriptions
final HashMap<String, AuthenticatorDescription> typeToAuthDescription =
@@ -87,7 +131,7 @@
// Read the validAccounts, if present, and add them to the setOfAllowableAccounts
Set<Account> setOfAllowableAccounts = null;
final ArrayList<Parcelable> validAccounts =
- getIntent().getParcelableArrayListExtra(EXTRA_ALLOWABLE_ACCOUNTS_ARRAYLIST);
+ intent.getParcelableArrayListExtra(EXTRA_ALLOWABLE_ACCOUNTS_ARRAYLIST);
if (validAccounts != null) {
setOfAllowableAccounts = new HashSet<Account>(validAccounts.size());
for (Parcelable parcelable : validAccounts) {
@@ -98,7 +142,7 @@
// Read the validAccountTypes, if present, and add them to the setOfAllowableAccountTypes
Set<String> setOfAllowableAccountTypes = null;
final ArrayList<String> validAccountTypes =
- getIntent().getStringArrayListExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_ARRAYLIST);
+ intent.getStringArrayListExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY);
if (validAccountTypes != null) {
setOfAllowableAccountTypes = new HashSet<String>(validAccountTypes.size());
for (String type : validAccountTypes) {
@@ -121,7 +165,8 @@
continue;
}
mAccountInfos.add(new AccountInfo(account,
- getDrawableForType(typeToAuthDescription, account.type)));
+ getDrawableForType(typeToAuthDescription, account.type),
+ account.equals(selectedAccount)));
}
// If there are no allowable accounts go directly to add account
@@ -131,7 +176,8 @@
}
// if there is only one allowable account return it
- if (mAccountInfos.size() == 1) {
+ if (!intent.getBooleanExtra(EXTRA_ALWAYS_PROMPT_FOR_ACCOUNT, false)
+ && mAccountInfos.size() == 1) {
Account account = mAccountInfos.get(0).account;
setResultAndFinish(account.name, account.type);
return;
@@ -143,7 +189,6 @@
list.setAdapter(new AccountArrayAdapter(this,
android.R.layout.simple_list_item_1, mAccountInfos));
list.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
- list.setTextFilterEnabled(false);
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
onListItemClick((ListView)parent, v, position, id);
@@ -173,10 +218,12 @@
return;
}
}
+ Log.d(TAG, "ChooseTypeAndAccountActivity.onActivityResult: canceled");
setResult(Activity.RESULT_CANCELED);
finish();
}
+
private Drawable getDrawableForType(
final HashMap<String, AuthenticatorDescription> typeToAuthDescription,
String accountType) {
@@ -212,31 +259,40 @@
bundle.putString(AccountManager.KEY_ACCOUNT_NAME, accountName);
bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, accountType);
setResult(Activity.RESULT_OK, new Intent().putExtras(bundle));
+ Log.d(TAG, "ChooseTypeAndAccountActivity.setResultAndFinish: "
+ + "selected account " + accountName + ", " + accountType);
finish();
}
private void startChooseAccountTypeActivity() {
final Intent intent = new Intent(this, ChooseAccountTypeActivity.class);
- intent.putStringArrayListExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_ARRAYLIST,
- getIntent().getStringArrayListExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_ARRAYLIST));
+ intent.putStringArrayListExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY,
+ getIntent().getStringArrayListExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY));
intent.putExtra(EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE,
- getIntent().getBundleExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_ARRAYLIST));
+ getIntent().getBundleExtra(EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE));
+ intent.putExtra(EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY,
+ getIntent().getStringArrayExtra(EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY));
+ intent.putExtra(EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING,
+ getIntent().getStringArrayExtra(EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING));
startActivityForResult(intent, 0);
}
private static class AccountInfo {
final Account account;
final Drawable drawable;
+ private final boolean checked;
- AccountInfo(Account account, Drawable drawable) {
+ AccountInfo(Account account, Drawable drawable, boolean checked) {
this.account = account;
this.drawable = drawable;
+ this.checked = checked;
}
}
private static class ViewHolder {
ImageView icon;
TextView text;
+ ImageView checkmark;
}
private static class AccountArrayAdapter extends ArrayAdapter<AccountInfo> {
@@ -256,10 +312,11 @@
ViewHolder holder;
if (convertView == null) {
- convertView = mLayoutInflater.inflate(R.layout.choose_account_row, null);
+ convertView = mLayoutInflater.inflate(R.layout.choose_selected_account_row, null);
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.account_row_text);
holder.icon = (ImageView) convertView.findViewById(R.id.account_row_icon);
+ holder.checkmark = (ImageView) convertView.findViewById(R.id.account_row_checkmark);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
@@ -267,7 +324,9 @@
holder.text.setText(mInfos.get(position).account.name);
holder.icon.setImageDrawable(mInfos.get(position).drawable);
-
+ final int displayCheckmark =
+ mInfos.get(position).checked ? View.VISIBLE : View.INVISIBLE;
+ holder.checkmark.setVisibility(displayCheckmark);
return convertView;
}
}
diff --git a/core/java/android/view/VolumePanel.java b/core/java/android/view/VolumePanel.java
index fb87e23..122865e 100644
--- a/core/java/android/view/VolumePanel.java
+++ b/core/java/android/view/VolumePanel.java
@@ -34,10 +34,12 @@
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
+import android.os.RemoteException;
import android.os.Vibrator;
import android.provider.Settings;
import android.provider.Settings.System;
import android.util.Log;
+import android.view.WindowManager.LayoutParams;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
@@ -175,20 +177,8 @@
View view = mView = inflater.inflate(R.layout.volume_adjust, null);
mView.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
- // Dismiss the dialog if the user touches outside the visible area. This is not
- // handled by the usual dialog dismissing code because there is a region above
- // the panel (marginTop) that is still within the dialog.
- if (event.getAction() == MotionEvent.ACTION_DOWN) {
- int x = (int) event.getX();
- int y = (int) event.getY();
- if (x < mPanel.getLeft() || x > mPanel.getRight() || y < mPanel.getTop()
- || y > mPanel.getBottom()) {
- forceTimeout();
- return true;
- }
- }
resetTimeout();
- return true;
+ return false;
}
});
mPanel = (ViewGroup) mView.findViewById(R.id.visible_panel);
@@ -196,7 +186,15 @@
mMoreButton = (ImageView) mView.findViewById(R.id.expand_button);
mDivider = (ImageView) mView.findViewById(R.id.expand_button_divider);
- mDialog = new Dialog(context, R.style.Theme_Panel_Volume);
+ mDialog = new Dialog(context, R.style.Theme_Panel_Volume) {
+ public boolean onTouchEvent(MotionEvent event) {
+ if (isShowing() && event.getAction() == MotionEvent.ACTION_OUTSIDE) {
+ forceTimeout();
+ return true;
+ }
+ return false;
+ }
+ };
mDialog.setTitle("Volume control"); // No need to localize
mDialog.setContentView(mView);
mDialog.setOnDismissListener(new OnDismissListener() {
@@ -208,11 +206,17 @@
// Change some window properties
Window window = mDialog.getWindow();
window.setGravity(Gravity.TOP);
- WindowManager.LayoutParams lp = window.getAttributes();
+ LayoutParams lp = window.getAttributes();
lp.token = null;
- lp.type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
+ // Offset from the top
+ lp.y = mContext.getResources().getDimensionPixelOffset(
+ com.android.internal.R.dimen.volume_panel_top);
+ lp.type = LayoutParams.TYPE_VOLUME_OVERLAY;
+ lp.width = LayoutParams.WRAP_CONTENT;
+ lp.height = LayoutParams.WRAP_CONTENT;
window.setAttributes(lp);
- window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+ window.addFlags(LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
mToneGenerators = new ToneGenerator[AudioSystem.getNumStreamTypes()];
mVibrator = new Vibrator();
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 2d1eeda..f57c6c4 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -644,7 +644,7 @@
private Drawable mSelectHandleLeft;
private Drawable mSelectHandleRight;
- static final boolean USE_WEBKIT_RINGS = true;
+ static final boolean USE_WEBKIT_RINGS = false;
// the color used to highlight the touch rectangles
private static final int HIGHLIGHT_COLOR = 0x6633b5e5;
// the round corner for the highlight path
@@ -730,6 +730,7 @@
static final int SELECT_AT = 135;
static final int SCREEN_ON = 136;
static final int ENTER_FULLSCREEN_VIDEO = 137;
+ static final int UPDATE_SELECTION = 138;
private static final int FIRST_PACKAGE_MSG_ID = SCROLL_TO_MSG_ID;
private static final int LAST_PACKAGE_MSG_ID = SET_TOUCH_HIGHLIGHT_RECTS;
@@ -4062,8 +4063,11 @@
// state.
// If mNativeClass is 0, we should not reach here, so we do not
// need to check it again.
+ boolean pressed = (mTouchMode == TOUCH_SHORTPRESS_START_MODE
+ || mTouchMode == TOUCH_INIT_MODE
+ || mTouchMode == TOUCH_SHORTPRESS_MODE);
nativeRecordButtons(hasFocus() && hasWindowFocus(),
- (mTouchMode == TOUCH_SHORTPRESS_START_MODE && !USE_WEBKIT_RINGS)
+ (pressed && !USE_WEBKIT_RINGS)
|| mTrackballDown || mGotCenterDown, false);
drawCoreAndCursorRing(canvas, mBackgroundColor,
mDrawCursorRing && drawRings);
@@ -6531,6 +6535,8 @@
mLastTouchTime = eventTime;
mVelocityTracker = VelocityTracker.obtain();
mSnapScrollMode = SNAP_NONE;
+ mPrivateHandler.sendEmptyMessageDelayed(UPDATE_SELECTION,
+ ViewConfiguration.getTapTimeout());
}
private void startDrag() {
@@ -7193,10 +7199,15 @@
return mZoomManager.zoomOut();
}
+ /**
+ * This selects the best clickable target at mLastTouchX and mLastTouchY
+ * and calls showCursorTimed on the native side
+ */
private void updateSelection() {
if (mNativeClass == 0) {
return;
}
+ mPrivateHandler.removeMessages(UPDATE_SELECTION);
// mLastTouchX and mLastTouchY are the point in the current viewport
int contentX = viewToContentX(mLastTouchX + mScrollX);
int contentY = viewToContentY(mLastTouchY + mScrollY);
@@ -7296,6 +7307,7 @@
return;
}
mTouchMode = TOUCH_DONE_MODE;
+ updateSelection();
switchOutDrawHistory();
// mLastTouchX and mLastTouchY are the point in the current viewport
int contentX = viewToContentX(mLastTouchX + mScrollX);
@@ -8186,6 +8198,14 @@
SCROLL_SELECT_TEXT, SELECT_SCROLL_INTERVAL);
break;
}
+ case UPDATE_SELECTION: {
+ if (mTouchMode == TOUCH_INIT_MODE
+ || mTouchMode == TOUCH_SHORTPRESS_MODE
+ || mTouchMode == TOUCH_SHORTPRESS_START_MODE) {
+ updateSelection();
+ }
+ break;
+ }
case SWITCH_TO_SHORTPRESS: {
mInitialHitTestResult = null; // set by updateSelection()
if (mTouchMode == TOUCH_INIT_MODE) {
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index d9c22c4..5392c2e 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -277,10 +277,11 @@
* called, false otherwise is returned.
*/
public boolean performItemClick(View view, int position, long id) {
- view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
-
if (mOnItemClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
+ if (view != null) {
+ view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
+ }
mOnItemClickListener.onItemClick(this, view, position, id);
return true;
}
diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java
index 390002b..ba69288 100644
--- a/core/java/android/widget/GridLayout.java
+++ b/core/java/android/widget/GridLayout.java
@@ -87,19 +87,26 @@
* layout parameters. When the
* {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins}
* property is set, default margins around children are automatically
- * allocated based on the child's visual characteristics. Each of the
- * margins so defined may be independently overridden by an assignment
+ * allocated based on the prevailing UI style guide for the platform.
+ * Each of the margins so defined may be independently overridden by an assignment
* to the appropriate layout parameter.
+ * Default values will generally produce a reasonable spacing between components
+ * but values may change between different releases of the platform.
*
* <h4>Excess Space Distribution</h4>
*
+ * GridLayout's distribution of excess space is based on <em>priority</em>
+ * rather than <em>weight</em>.
+ * <p>
* A child's ability to stretch is inferred from the alignment properties of
* its row and column groups (which are typically set by setting the
* {@link LayoutParams#setGravity(int) gravity} property of the child's layout parameters).
* If alignment was defined along a given axis then the component
- * is taken as flexible in along that axis. If no alignment was set,
- * the component is instead assumed to be inflexible. Multiple components in
- * the same row or column group are considered to act in <em>parallel</em>. Such a
+ * is taken as <em>flexible</em> in that direction. If no alignment was set,
+ * the component is instead assumed to be <em>inflexible</em>.
+ * <p>
+ * Multiple components in the same row or column group are
+ * considered to act in <em>parallel</em>. Such a
* group is flexible only if <em>all</em> of the components
* within it are flexible. Row and column groups that sit either side of a common boundary
* are instead considered to act in <em>series</em>. The composite group made of these two
@@ -109,6 +116,23 @@
* gravity. To prevent a column from stretching, ensure that one of the components
* in the column does not define a gravity.
* <p>
+ * When the principle of flexibility does not provide complete disambiguation,
+ * GridLayout's algorithms favour rows and columns that are closer to its <em>right</em>
+ * and <em>bottom</em> edges.
+ *
+ * <h5>Limitations</h5>
+ *
+ * GridLayout does not provide support for the principle of <em>weight</em>, as defined in
+ * {@link LinearLayout.LayoutParams#weight}. In general, it is not therefore possible
+ * to configure a GridLayout to distribute excess space in non-trivial proportions between
+ * multiple rows or columns.
+ * <p>
+ * Some common use-cases may nevertheless be accommodated as follows.
+ * To place equal amounts of space around a component in a cell group;
+ * use {@link #CENTER} alignment (or {@link LayoutParams#setGravity(int) gravity}).
+ * For complete control over excess space distribution in a row or column;
+ * use a {@link LinearLayout} subview to hold the components in the associated cell group.
+ * When using either of these techniques, bear in mind that cell groups may be defined to overlap.
* <p>
* See {@link GridLayout.LayoutParams} for a full description of the
* layout parameters used by GridLayout.
@@ -180,9 +204,11 @@
// Misc constants
- private static final String TAG = GridLayout.class.getName();
- private static boolean DEBUG = false;
- private static final int PRF = 1;
+ static final String TAG = GridLayout.class.getName();
+ static final boolean DEBUG = false;
+ static final int PRF = 1;
+ static final int MAX_SIZE = 100000;
+ static final int DEFAULT_CONTAINER_MARGIN = 0;
// Defaults
@@ -191,8 +217,6 @@
private static final boolean DEFAULT_USE_DEFAULT_MARGINS = false;
private static final boolean DEFAULT_ORDER_PRESERVED = true;
private static final int DEFAULT_ALIGNMENT_MODE = ALIGN_MARGINS;
- private static final int DEFAULT_CONTAINER_MARGIN = 0;
- private static final int MAX_SIZE = 100000;
// TypedArray indices
@@ -206,13 +230,13 @@
// Instance variables
- private final Axis mHorizontalAxis = new Axis(true);
- private final Axis mVerticalAxis = new Axis(false);
- private boolean mLayoutParamsValid = false;
- private int mOrientation = DEFAULT_ORIENTATION;
- private boolean mUseDefaultMargins = DEFAULT_USE_DEFAULT_MARGINS;
- private int mAlignmentMode = DEFAULT_ALIGNMENT_MODE;
- private int mDefaultGap;
+ final Axis horizontalAxis = new Axis(true);
+ final Axis verticalAxis = new Axis(false);
+ boolean layoutParamsValid = false;
+ int orientation = DEFAULT_ORIENTATION;
+ boolean useDefaultMargins = DEFAULT_USE_DEFAULT_MARGINS;
+ int alignmentMode = DEFAULT_ALIGNMENT_MODE;
+ int defaultGap;
// Constructors
@@ -224,7 +248,7 @@
if (DEBUG) {
setWillNotDraw(false);
}
- mDefaultGap = context.getResources().getDimensionPixelOffset(R.dimen.default_gap);
+ defaultGap = context.getResources().getDimensionPixelOffset(R.dimen.default_gap);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GridLayout);
try {
setRowCount(a.getInt(ROW_COUNT, DEFAULT_COUNT));
@@ -266,13 +290,12 @@
* @attr ref android.R.styleable#GridLayout_orientation
*/
public int getOrientation() {
- return mOrientation;
+ return orientation;
}
/**
- * The orientation property does not affect layout. Orientation is used
- * only to generate default row/column indices when they are not specified
- * by a component's layout parameters.
+ * Orientation is used only to generate default row/column indices when
+ * they are not specified by a component's layout parameters.
* <p>
* The default value of this property is {@link #HORIZONTAL}.
*
@@ -283,8 +306,9 @@
* @attr ref android.R.styleable#GridLayout_orientation
*/
public void setOrientation(int orientation) {
- if (mOrientation != orientation) {
- mOrientation = orientation;
+ if (this.orientation != orientation) {
+ this.orientation = orientation;
+ invalidateStructure();
requestLayout();
}
}
@@ -302,13 +326,12 @@
* @attr ref android.R.styleable#GridLayout_rowCount
*/
public int getRowCount() {
- return mVerticalAxis.getCount();
+ return verticalAxis.getCount();
}
/**
- * The rowCount property does not affect layout. RowCount is used
- * only to generate default row/column indices when they are not specified
- * by a component's layout parameters.
+ * RowCount is used only to generate default row/column indices when
+ * they are not specified by a component's layout parameters.
*
* @param rowCount the number of rows
*
@@ -318,7 +341,9 @@
* @attr ref android.R.styleable#GridLayout_rowCount
*/
public void setRowCount(int rowCount) {
- mVerticalAxis.setCount(rowCount);
+ verticalAxis.setCount(rowCount);
+ invalidateStructure();
+ requestLayout();
}
/**
@@ -334,13 +359,12 @@
* @attr ref android.R.styleable#GridLayout_columnCount
*/
public int getColumnCount() {
- return mHorizontalAxis.getCount();
+ return horizontalAxis.getCount();
}
/**
- * The columnCount property does not affect layout. ColumnCount is used
- * only to generate default column/column indices when they are not specified
- * by a component's layout parameters.
+ * ColumnCount is used only to generate default column/column indices when
+ * they are not specified by a component's layout parameters.
*
* @param columnCount the number of columns.
*
@@ -350,7 +374,9 @@
* @attr ref android.R.styleable#GridLayout_columnCount
*/
public void setColumnCount(int columnCount) {
- mHorizontalAxis.setCount(columnCount);
+ horizontalAxis.setCount(columnCount);
+ invalidateStructure();
+ requestLayout();
}
/**
@@ -364,7 +390,7 @@
* @attr ref android.R.styleable#GridLayout_useDefaultMargins
*/
public boolean getUseDefaultMargins() {
- return mUseDefaultMargins;
+ return useDefaultMargins;
}
/**
@@ -394,7 +420,7 @@
* @attr ref android.R.styleable#GridLayout_useDefaultMargins
*/
public void setUseDefaultMargins(boolean useDefaultMargins) {
- mUseDefaultMargins = useDefaultMargins;
+ this.useDefaultMargins = useDefaultMargins;
requestLayout();
}
@@ -411,7 +437,7 @@
* @attr ref android.R.styleable#GridLayout_alignmentMode
*/
public int getAlignmentMode() {
- return mAlignmentMode;
+ return alignmentMode;
}
/**
@@ -430,7 +456,7 @@
* @attr ref android.R.styleable#GridLayout_alignmentMode
*/
public void setAlignmentMode(int alignmentMode) {
- mAlignmentMode = alignmentMode;
+ this.alignmentMode = alignmentMode;
requestLayout();
}
@@ -445,7 +471,7 @@
* @attr ref android.R.styleable#GridLayout_rowOrderPreserved
*/
public boolean isRowOrderPreserved() {
- return mVerticalAxis.isOrderPreserved();
+ return verticalAxis.isOrderPreserved();
}
/**
@@ -465,7 +491,7 @@
* @attr ref android.R.styleable#GridLayout_rowOrderPreserved
*/
public void setRowOrderPreserved(boolean rowOrderPreserved) {
- mVerticalAxis.setOrderPreserved(rowOrderPreserved);
+ verticalAxis.setOrderPreserved(rowOrderPreserved);
invalidateStructure();
requestLayout();
}
@@ -481,7 +507,7 @@
* @attr ref android.R.styleable#GridLayout_columnOrderPreserved
*/
public boolean isColumnOrderPreserved() {
- return mHorizontalAxis.isOrderPreserved();
+ return horizontalAxis.isOrderPreserved();
}
/**
@@ -501,14 +527,14 @@
* @attr ref android.R.styleable#GridLayout_columnOrderPreserved
*/
public void setColumnOrderPreserved(boolean columnOrderPreserved) {
- mHorizontalAxis.setOrderPreserved(columnOrderPreserved);
+ horizontalAxis.setOrderPreserved(columnOrderPreserved);
invalidateStructure();
requestLayout();
}
// Static utility methods
- private static int max2(int[] a, int valueIfEmpty) {
+ static int max2(int[] a, int valueIfEmpty) {
int result = valueIfEmpty;
for (int i = 0, N = a.length; i < N; i++) {
result = Math.max(result, a[i]);
@@ -517,14 +543,14 @@
}
@SuppressWarnings("unchecked")
- private static <T> T[] append(T[] a, T[] b) {
+ static <T> T[] append(T[] a, T[] b) {
T[] result = (T[]) Array.newInstance(a.getClass().getComponentType(), a.length + b.length);
System.arraycopy(a, 0, result, 0, a.length);
System.arraycopy(b, 0, result, a.length, b.length);
return result;
}
- private static Alignment getAlignment(int gravity, boolean horizontal) {
+ static Alignment getAlignment(int gravity, boolean horizontal) {
int mask = horizontal ? HORIZONTAL_GRAVITY_MASK : VERTICAL_GRAVITY_MASK;
int shift = horizontal ? AXIS_X_SHIFT : AXIS_Y_SHIFT;
int flags = (gravity & mask) >> shift;
@@ -547,7 +573,7 @@
if (c.getClass() == Space.class) {
return 0;
}
- return mDefaultGap / 2;
+ return defaultGap / 2;
}
private int getDefaultMargin(View c, boolean isAtEdge, boolean horizontal, boolean leading) {
@@ -555,18 +581,18 @@
}
private int getDefaultMarginValue(View c, LayoutParams p, boolean horizontal, boolean leading) {
- if (!mUseDefaultMargins) {
+ if (!useDefaultMargins) {
return 0;
}
Spec spec = horizontal ? p.columnSpec : p.rowSpec;
- Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis;
+ Axis axis = horizontal ? horizontalAxis : verticalAxis;
Interval span = spec.span;
boolean isAtEdge = leading ? (span.min == 0) : (span.max == axis.getCount());
return getDefaultMargin(c, isAtEdge, horizontal, leading);
}
- private int getMargin1(View view, boolean horizontal, boolean leading) {
+ int getMargin1(View view, boolean horizontal, boolean leading) {
LayoutParams lp = getLayoutParams(view);
int margin = horizontal ?
(leading ? lp.leftMargin : lp.rightMargin) :
@@ -575,10 +601,10 @@
}
private int getMargin(View view, boolean horizontal, boolean leading) {
- if (mAlignmentMode == ALIGN_MARGINS) {
+ if (alignmentMode == ALIGN_MARGINS) {
return getMargin1(view, horizontal, leading);
} else {
- Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis;
+ Axis axis = horizontal ? horizontalAxis : verticalAxis;
int[] margins = leading ? axis.getLeadingMargins() : axis.getTrailingMargins();
LayoutParams lp = getLayoutParams(view);
Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
@@ -591,10 +617,6 @@
return getMargin(child, horizontal, true) + getMargin(child, horizontal, false);
}
- private static int valueIfDefined(int value, int defaultValue) {
- return (value != UNDEFINED) ? value : defaultValue;
- }
-
private static boolean fits(int[] a, int value, int start, int end) {
if (end > a.length) {
return false;
@@ -629,9 +651,9 @@
// install default indices for cells that don't define them
private void validateLayoutParams() {
- final boolean horizontal = (mOrientation == HORIZONTAL);
- final int axisCount = horizontal ? mHorizontalAxis.count : mVerticalAxis.count;
- final int count = valueIfDefined(axisCount, 0);
+ final boolean horizontal = (orientation == HORIZONTAL);
+ final Axis axis = horizontal ? horizontalAxis : verticalAxis;
+ final int count = (axis.definedCount != UNDEFINED) ? axis.definedCount : 0;
int major = 0;
int minor = 0;
@@ -640,15 +662,17 @@
for (int i = 0, N = getChildCount(); i < N; i++) {
LayoutParams lp = getLayoutParams1(getChildAt(i));
- final Interval majorRange = (horizontal ? lp.rowSpec : lp.columnSpec).span;
- final boolean majorWasDefined = (majorRange.min != UNDEFINED);
+ final Spec majorSpec = horizontal ? lp.rowSpec : lp.columnSpec;
+ final Interval majorRange = majorSpec.span;
+ final boolean majorWasDefined = majorSpec.startDefined;
final int majorSpan = majorRange.size();
if (majorWasDefined) {
major = majorRange.min;
}
- final Interval minorRange = (horizontal ? lp.columnSpec : lp.rowSpec).span;
- final boolean minorWasDefined = (minorRange.min != UNDEFINED);
+ final Spec minorSpec = horizontal ? lp.columnSpec : lp.rowSpec;
+ final Interval minorRange = minorSpec.span;
+ final boolean minorWasDefined = minorSpec.startDefined;
final int minorSpan = clip(minorRange, minorWasDefined, count);
if (minorWasDefined) {
minor = minorRange.min;
@@ -685,9 +709,9 @@
}
private void invalidateStructure() {
- mLayoutParamsValid = false;
- mHorizontalAxis.invalidateStructure();
- mVerticalAxis.invalidateStructure();
+ layoutParamsValid = false;
+ horizontalAxis.invalidateStructure();
+ verticalAxis.invalidateStructure();
// This can end up being done twice. Better twice than not at all.
invalidateValues();
}
@@ -695,9 +719,9 @@
private void invalidateValues() {
// Need null check because requestLayout() is called in View's initializer,
// before we are set up.
- if (mHorizontalAxis != null && mVerticalAxis != null) {
- mHorizontalAxis.invalidateValues();
- mVerticalAxis.invalidateValues();
+ if (horizontalAxis != null && verticalAxis != null) {
+ horizontalAxis.invalidateValues();
+ verticalAxis.invalidateValues();
}
}
@@ -705,10 +729,10 @@
return (LayoutParams) c.getLayoutParams();
}
- private LayoutParams getLayoutParams(View c) {
- if (!mLayoutParamsValid) {
+ final LayoutParams getLayoutParams(View c) {
+ if (!layoutParamsValid) {
validateLayoutParams();
- mLayoutParamsValid = true;
+ layoutParamsValid = true;
}
return getLayoutParams1(c);
}
@@ -752,7 +776,7 @@
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.argb(50, 255, 255, 255));
- int[] xs = mHorizontalAxis.locations;
+ int[] xs = horizontalAxis.locations;
if (xs != null) {
for (int i = 0, length = xs.length; i < length; i++) {
int x = xs[i];
@@ -760,7 +784,7 @@
}
}
- int[] ys = mVerticalAxis.locations;
+ int[] ys = verticalAxis.locations;
if (ys != null) {
for (int i = 0, length = ys.length; i < length; i++) {
int y = ys[i];
@@ -822,7 +846,7 @@
// Measurement
- private boolean isGone(View c) {
+ final boolean isGone(View c) {
return c.getVisibility() == View.GONE;
}
@@ -847,8 +871,8 @@
protected void onMeasure(int widthSpec, int heightSpec) {
measureChildrenWithMargins(widthSpec, heightSpec);
- int width = getPaddingLeft() + mHorizontalAxis.getMeasure(widthSpec) + getPaddingRight();
- int height = getPaddingTop() + mVerticalAxis.getMeasure(heightSpec) + getPaddingBottom();
+ int width = getPaddingLeft() + horizontalAxis.getMeasure(widthSpec) + getPaddingRight();
+ int height = getPaddingTop() + verticalAxis.getMeasure(heightSpec) + getPaddingBottom();
int measuredWidth = Math.max(width, getSuggestedMinimumWidth());
int measuredHeight = Math.max(height, getSuggestedMinimumHeight());
@@ -866,7 +890,7 @@
return horizontal ? c.getMeasuredWidth() : c.getMeasuredHeight();
}
- private int getMeasurementIncludingMargin(View c, boolean horizontal) {
+ final int getMeasurementIncludingMargin(View c, boolean horizontal) {
if (isGone(c)) {
return 0;
}
@@ -879,7 +903,7 @@
invalidateValues();
}
- private Alignment getAlignment(Alignment alignment, boolean horizontal) {
+ final Alignment getAlignment(Alignment alignment, boolean horizontal) {
return (alignment != UNDEFINED_ALIGNMENT) ? alignment :
(horizontal ? LEFT : BASELINE);
}
@@ -908,11 +932,11 @@
int paddingRight = getPaddingRight();
int paddingBottom = getPaddingBottom();
- mHorizontalAxis.layout(targetWidth - paddingLeft - paddingRight);
- mVerticalAxis.layout(targetHeight - paddingTop - paddingBottom);
+ horizontalAxis.layout(targetWidth - paddingLeft - paddingRight);
+ verticalAxis.layout(targetHeight - paddingTop - paddingBottom);
- int[] hLocations = mHorizontalAxis.getLocations();
- int[] vLocations = mVerticalAxis.getLocations();
+ int[] hLocations = horizontalAxis.getLocations();
+ int[] vLocations = verticalAxis.getLocations();
for (int i = 0, N = getChildCount(); i < N; i++) {
View c = getChildAt(i);
@@ -941,8 +965,8 @@
int dx, dy;
- Bounds colBounds = mHorizontalAxis.getGroupBounds().getValue(i);
- Bounds rowBounds = mVerticalAxis.getGroupBounds().getValue(i);
+ Bounds colBounds = horizontalAxis.getGroupBounds().getValue(i);
+ Bounds rowBounds = verticalAxis.getGroupBounds().getValue(i);
// Gravity offsets: the location of the alignment group relative to its cell group.
//noinspection NullableProblems
@@ -990,7 +1014,7 @@
distinguished by the "horizontal" flag which is true for the horizontal axis and false
for the vertical one.
*/
- private class Axis {
+ final class Axis {
private static final int MIN_VALUE = -1000000;
private static final int NEW = 0;
@@ -999,9 +1023,8 @@
public final boolean horizontal;
- public int count = UNDEFINED;
- public boolean countValid = false;
- public boolean countWasExplicitySet = false;
+ public int definedCount = UNDEFINED;
+ private int inferredCount = UNDEFINED;
PackedMap<Spec, Bounds> groupBounds;
public boolean groupBoundsValid = false;
@@ -1024,7 +1047,7 @@
public int[] locations;
public boolean locationsValid = false;
- private boolean mOrderPreserved = DEFAULT_ORDER_PRESERVED;
+ boolean orderPreserved = DEFAULT_ORDER_PRESERVED;
private MutableInt parentMin = new MutableInt(0);
private MutableInt parentMax = new MutableInt(-MAX_SIZE);
@@ -1046,25 +1069,27 @@
return count == -1 ? UNDEFINED : count;
}
- public int getCount() {
- if (!countValid) {
- count = max(0, maxIndex()); // if there are no cells, the count is zero
- countValid = true;
+ private int getInferredCount() {
+ if (inferredCount == UNDEFINED) {
+ inferredCount = max(0, maxIndex()); // if there are no cells, actual count is zero
}
- return count;
+ return inferredCount;
+ }
+
+ public int getCount() {
+ return max(definedCount, getInferredCount());
}
public void setCount(int count) {
- this.count = count;
- this.countWasExplicitySet = count != UNDEFINED;
+ this.definedCount = count;
}
public boolean isOrderPreserved() {
- return mOrderPreserved;
+ return orderPreserved;
}
public void setOrderPreserved(boolean orderPreserved) {
- mOrderPreserved = orderPreserved;
+ this.orderPreserved = orderPreserved;
invalidateStructure();
}
@@ -1093,7 +1118,7 @@
}
}
- private PackedMap<Spec, Bounds> getGroupBounds() {
+ public PackedMap<Spec, Bounds> getGroupBounds() {
if (groupBounds == null) {
groupBounds = createGroupBounds();
}
@@ -1183,7 +1208,7 @@
// Group arcs by their first vertex, returning an array of arrays.
// This is linear in the number of arcs.
- private Arc[][] groupArcsByFirstVertex(Arc[] arcs) {
+ Arc[][] groupArcsByFirstVertex(Arc[] arcs) {
int N = getCount() + 1; // the number of vertices
Arc[][] result = new Arc[N][];
int[] sizes = new int[N];
@@ -1262,7 +1287,7 @@
addComponentSizes(maxs, getBackwardLinks());
// Add ordering constraints to prevent row/col sizes from going negative
- if (mOrderPreserved) {
+ if (orderPreserved) {
// Add a constraint for every row/col
for (int i = 0; i < getCount(); i++) {
include(mins, new Interval(i, i + 1), new MutableInt(0));
@@ -1315,6 +1340,48 @@
return false;
}
+ private void init(int[] locations) {
+ Arrays.fill(locations, MIN_VALUE);
+ locations[0] = 0;
+ }
+
+ private String arcsToString(List<Arc> arcs) {
+ String var = horizontal ? "c" : "r";
+ StringBuilder result = new StringBuilder();
+ boolean first = false;
+ for(Arc arc : arcs) {
+ if (!first) {
+ first = true;
+ } else {
+ result =result.append(", ");
+ }
+ int src = arc.span.min;
+ int dst = arc.span.max;
+ int value = arc.value.value;
+ result.append((src < dst) ?
+ var + dst + " - " + var + src + " > " + value :
+ var + src + " - " + var + dst + " < " + -value);
+
+ }
+ return result.toString();
+ }
+
+ private void logError(String axisName, Arc[] arcs, boolean[] culprits0) {
+ List<Arc> culprits = new ArrayList<Arc>();
+ List<Arc> removed = new ArrayList<Arc>();
+ for (int c = 0; c < arcs.length; c++) {
+ Arc arc = arcs[c];
+ if (culprits0[c]) {
+ culprits.add(arc);
+ }
+ if (!arc.valid) {
+ removed.add(arc);
+ }
+ }
+ Log.d(TAG, axisName + " constraints: " + arcsToString(culprits) + " are inconsistent; "
+ + "permanently removing: " + arcsToString(removed) + ". ");
+ }
+
/*
Bellman-Ford variant - modified to reduce typical running time from O(N^2) to O(N)
@@ -1350,51 +1417,54 @@
completes in O(N) steps with very low constants.
*/
private void solve(Arc[] arcs, int[] locations) {
- String axis = horizontal ? "horizontal" : "vertical";
+ String axisName = horizontal ? "horizontal" : "vertical";
int N = getCount() + 1; // The number of vertices is the number of columns/rows + 1.
+ boolean[] originalCulprits = null;
- // We take one extra pass over traditional Bellman-Ford (and omit their final step)
- for (int i = 0; i < N; i++) {
- boolean changed = false;
- for (int j = 0, length = arcs.length; j < length; j++) {
- changed |= relax(locations, arcs[j]);
- }
- if (!changed) {
- if (DEBUG) {
- Log.v(TAG, axis + " iteration completed in " + (1 + i) + " steps of " + N);
+ for (int p = 0; p < arcs.length; p++) {
+ init(locations);
+
+ // We take one extra pass over traditional Bellman-Ford (and omit their final step)
+ for (int i = 0; i < N; i++) {
+ boolean changed = false;
+ for (int j = 0, length = arcs.length; j < length; j++) {
+ changed |= relax(locations, arcs[j]);
}
- return;
- }
- }
-
- Log.d(TAG, "The " + axis + " constraints contained a contradiction. Resolving... ");
- Log.d(TAG, Arrays.toString(arcs));
-
- boolean[] culprits = new boolean[arcs.length];
- for (int i = 0; i < N; i++) {
- for (int j = 0, length = arcs.length; j < length; j++) {
- culprits[j] |= relax(locations, arcs[j]);
- }
- }
- for (int i = 0; i < culprits.length; i++) {
- if (culprits[i]) {
- Arc arc = arcs[i];
- // Only remove max values, min values alone cannot be inconsistent
- if (arc.span.min < arc.span.max) {
- continue;
+ if (!changed) {
+ if (originalCulprits != null) {
+ logError(axisName, arcs, originalCulprits);
+ }
+ if (DEBUG) {
+ Log.v(TAG, axisName + " iteration completed in " +
+ (1 + i) + " steps of " + N);
+ }
+ return;
}
- Log.d(TAG, "Removing: " + arc);
- arc.valid = false;
- break;
+ }
+
+ boolean[] culprits = new boolean[arcs.length];
+ for (int i = 0; i < N; i++) {
+ for (int j = 0, length = arcs.length; j < length; j++) {
+ culprits[j] |= relax(locations, arcs[j]);
+ }
+ }
+
+ if (p == 0) {
+ originalCulprits = culprits;
+ }
+
+ for (int i = 0; i < arcs.length; i++) {
+ if (culprits[i]) {
+ Arc arc = arcs[i];
+ // Only remove max values, min values alone cannot be inconsistent
+ if (arc.span.min < arc.span.max) {
+ continue;
+ }
+ arc.valid = false;
+ break;
+ }
}
}
- solve1(arcs, locations);
- }
-
- private void solve1(Arc[] arcs, int[] a) {
- Arrays.fill(a, MIN_VALUE);
- a[0] = 0;
- solve(arcs, a);
}
private void computeMargins(boolean leading) {
@@ -1410,7 +1480,9 @@
}
}
- private int[] getLeadingMargins() {
+ // External entry points
+
+ public int[] getLeadingMargins() {
if (leadingMargins == null) {
leadingMargins = new int[getCount() + 1];
}
@@ -1421,7 +1493,7 @@
return leadingMargins;
}
- private int[] getTrailingMargins() {
+ public int[] getTrailingMargins() {
if (trailingMargins == null) {
trailingMargins = new int[getCount() + 1];
}
@@ -1433,10 +1505,10 @@
}
private void computeLocations(int[] a) {
- solve1(getArcs(), a);
+ solve(getArcs(), a);
}
- private int[] getLocations() {
+ public int[] getLocations() {
if (locations == null) {
int N = getCount() + 1;
locations = new int[N];
@@ -1448,8 +1520,6 @@
return locations;
}
- // External entry points
-
private int size(int[] locations) {
return max2(locations, 0) - locations[0];
}
@@ -1465,7 +1535,7 @@
return size(getLocations());
}
- private int getMeasure(int measureSpec) {
+ public int getMeasure(int measureSpec) {
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
switch (mode) {
@@ -1485,13 +1555,13 @@
}
}
- private void layout(int size) {
+ public void layout(int size) {
setParentConstraints(size, size);
getLocations();
}
- private void invalidateStructure() {
- countValid = false;
+ public void invalidateStructure() {
+ inferredCount = UNDEFINED;
groupBounds = null;
forwardLinks = null;
@@ -1506,7 +1576,7 @@
invalidateValues();
}
- private void invalidateValues() {
+ public void invalidateValues() {
groupBoundsValid = false;
forwardLinksValid = false;
backwardLinksValid = false;
@@ -1536,9 +1606,22 @@
* both aspects of alignment within the cell group. It is also possible to specify a child's
* alignment within its cell group by using the {@link GridLayout.LayoutParams#setGravity(int)}
* method.
- * <p>
- * See {@link GridLayout} for a description of the conventions used by GridLayout
- * in reference to grid indices.
+ *
+ * <h4>WRAP_CONTENT and MATCH_PARENT</h4>
+ *
+ * Because the default values of the {@link #width} and {@link #height}
+ * properties are both {@link #WRAP_CONTENT}, this value never needs to be explicitly
+ * declared in the layout parameters of GridLayout's children. In addition,
+ * GridLayout does not distinguish the special size value {@link #MATCH_PARENT} from
+ * {@link #WRAP_CONTENT}. A component's ability to expand to the size of the parent is
+ * instead controlled by the principle of <em>flexibility</em>,
+ * as discussed in {@link GridLayout}.
+ *
+ * <h4>Summary</h4>
+ *
+ * You should not need to use either of the special size values:
+ * {@code WRAP_CONTENT} or {@code MATCH_PARENT} when configuring the children of
+ * a GridLayout.
*
* <h4>Default values</h4>
*
@@ -1561,12 +1644,17 @@
* {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is
* {@code false}; otherwise {@link #UNDEFINED}, to
* indicate that a default value should be computed on demand. </li>
- * <li>{@link #rowSpec}{@code .span} = {@code [0, 1]} </li>
- * <li>{@link #rowSpec}{@code .alignment} = {@link #BASELINE} </li>
- * <li>{@link #columnSpec}{@code .span} = {@code [0, 1]} </li>
- * <li>{@link #columnSpec}{@code .alignment} = {@link #LEFT} </li>
+ * <li>{@link #rowSpec}<code>.row</code> = {@link #UNDEFINED} </li>
+ * <li>{@link #rowSpec}<code>.rowSpan</code> = 1 </li>
+ * <li>{@link #rowSpec}<code>.alignment</code> = {@link #BASELINE} </li>
+ * <li>{@link #columnSpec}<code>.column</code> = {@link #UNDEFINED} </li>
+ * <li>{@link #columnSpec}<code>.columnSpan</code> = 1 </li>
+ * <li>{@link #columnSpec}<code>.alignment</code> = {@link #LEFT} </li>
* </ul>
*
+ * See {@link GridLayout} for a more complete description of the conventions
+ * used by GridLayout in the interpretation of the properties of this class.
+ *
* @attr ref android.R.styleable#GridLayout_Layout_layout_row
* @attr ref android.R.styleable#GridLayout_Layout_layout_rowSpan
* @attr ref android.R.styleable#GridLayout_Layout_layout_column
@@ -1606,15 +1694,16 @@
// Instance variables
/**
- * The spec that specifies the vertical characteristics of the cell group
+ * The spec that defines the vertical characteristics of the cell group
* described by these layout parameters.
*/
- public Spec rowSpec;
+ public Spec rowSpec = Spec.UNDEFINED;
+
/**
- * The spec that specifies the horizontal characteristics of the cell group
+ * The spec that defines the horizontal characteristics of the cell group
* described by these layout parameters.
*/
- public Spec columnSpec;
+ public Spec columnSpec = Spec.UNDEFINED;
// Constructors
@@ -1646,7 +1735,7 @@
* Constructs a new LayoutParams with default values as defined in {@link LayoutParams}.
*/
public LayoutParams() {
- this(spec(UNDEFINED), spec(UNDEFINED));
+ this(Spec.UNDEFINED, Spec.UNDEFINED);
}
// Copying constructors
@@ -1670,8 +1759,8 @@
*/
public LayoutParams(LayoutParams that) {
super(that);
- this.rowSpec = new Spec(that.rowSpec);
- this.columnSpec = new Spec(that.columnSpec);
+ this.rowSpec = that.rowSpec;
+ this.columnSpec = that.columnSpec;
}
// AttributeSet constructors
@@ -1750,11 +1839,11 @@
this.height = attributes.getLayoutDimension(heightAttr, DEFAULT_HEIGHT);
}
- private void setRowSpecSpan(Interval span) {
+ final void setRowSpecSpan(Interval span) {
rowSpec = rowSpec.copyWriteSpan(span);
}
- private void setColumnSpecSpan(Interval span) {
+ final void setColumnSpecSpan(Interval span) {
columnSpec = columnSpec.copyWriteSpan(span);
}
}
@@ -1763,7 +1852,7 @@
In place of a HashMap from span to Int, use an array of key/value pairs - stored in Arcs.
Add the mutables completesCycle flag to avoid creating another hash table for detecting cycles.
*/
- private static class Arc {
+ final static class Arc {
public final Interval span;
public final MutableInt value;
public boolean valid = true;
@@ -1781,18 +1870,18 @@
// A mutable Integer - used to avoid heap allocation during the layout operation
- private static class MutableInt {
+ final static class MutableInt {
public int value;
- private MutableInt() {
+ public MutableInt() {
reset();
}
- private MutableInt(int value) {
+ public MutableInt(int value) {
this.value = value;
}
- private void reset() {
+ public void reset() {
value = Integer.MIN_VALUE;
}
@@ -1802,7 +1891,7 @@
}
}
- private static class Assoc<K, V> extends ArrayList<Pair<K, V>> {
+ final static class Assoc<K, V> extends ArrayList<Pair<K, V>> {
private final Class<K> keyType;
private final Class<V> valueType;
@@ -1811,7 +1900,7 @@
this.valueType = valueType;
}
- private static <K, V> Assoc<K, V> of(Class<K> keyType, Class<V> valueType) {
+ public static <K, V> Assoc<K, V> of(Class<K> keyType, Class<V> valueType) {
return new Assoc<K, V>(keyType, valueType);
}
@@ -1847,7 +1936,7 @@
rather than using (and storing) an implementation of Map<Key, ?>.
*/
@SuppressWarnings(value = "unchecked")
- private static class PackedMap<K, V> {
+ final static class PackedMap<K, V> {
public final int[] index;
public final K[] keys;
public final V[] values;
@@ -1859,7 +1948,7 @@
this.values = compact(values, index);
}
- private V getValue(int i) {
+ public V getValue(int i) {
return values[index[i]];
}
@@ -1907,7 +1996,7 @@
group to Bounds and to loop through all Views in the group taking the maximum
of the values for each View.
*/
- private static class Bounds {
+ static class Bounds {
public int before;
public int after;
public int flexibility; // we're flexible iff all included specs are flexible
@@ -1969,7 +2058,7 @@
* Intervals are often written as {@code [min, max]} and represent the set of values
* {@code x} such that {@code min <= x < max}.
*/
- static class Interval {
+ final static class Interval {
/**
* The minimum value.
*/
@@ -1995,11 +2084,11 @@
this.max = max;
}
- private int size() {
+ int size() {
return max - min;
}
- private Interval inverse() {
+ Interval inverse() {
return new Interval(max, min);
}
@@ -2062,32 +2151,31 @@
* For column groups, this specifies the horizontal alignment.
*/
public static class Spec {
+ static final Spec UNDEFINED = spec(GridLayout.UNDEFINED);
+
+ final boolean startDefined;
final Interval span;
final Alignment alignment;
- private Spec(Interval span, Alignment alignment) {
+ private Spec(boolean startDefined, Interval span, Alignment alignment) {
+ this.startDefined = startDefined;
this.span = span;
this.alignment = alignment;
}
- /* Copying constructor */
- private Spec(Spec that) {
- this(that.span, that.alignment);
+ private Spec(boolean startDefined, int start, int size, Alignment alignment) {
+ this(startDefined, new Interval(start, start + size), alignment);
}
- private Spec(int start, int size, Alignment alignment) {
- this(new Interval(start, start + size), alignment);
+ final Spec copyWriteSpan(Interval span) {
+ return new Spec(startDefined, span, alignment);
}
- private Spec copyWriteSpan(Interval span) {
- return new Spec(span, alignment);
+ final Spec copyWriteAlignment(Alignment alignment) {
+ return new Spec(startDefined, span, alignment);
}
- private Spec copyWriteAlignment(Alignment alignment) {
- return new Spec(span, alignment);
- }
-
- int getFlexibility() {
+ final int getFlexibility() {
return (alignment == UNDEFINED_ALIGNMENT) ? INFLEXIBLE : CAN_STRETCH;
}
@@ -2143,7 +2231,7 @@
* @param alignment the alignment
*/
public static Spec spec(int start, int size, Alignment alignment) {
- return new Spec(start, size, alignment);
+ return new Spec(start != UNDEFINED, start, size, alignment);
}
/**
@@ -2246,7 +2334,7 @@
}
}
- private static final Alignment UNDEFINED_ALIGNMENT = new Alignment() {
+ static final Alignment UNDEFINED_ALIGNMENT = new Alignment() {
public int getAlignmentValue(View view, int viewSize) {
return UNDEFINED;
}
@@ -2367,7 +2455,7 @@
}
};
- private static boolean canStretch(int flexibility) {
+ static boolean canStretch(int flexibility) {
return (flexibility & CAN_STRETCH) != 0;
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 58e0edd..af820ac 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -1193,6 +1193,11 @@
}
super.setEnabled(enabled);
prepareCursorControllers();
+ if (enabled) {
+ // Make sure IME is updated with current editor info.
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null) imm.restartInput(this);
+ }
}
/**
diff --git a/core/java/com/android/internal/widget/TransportControlView.java b/core/java/com/android/internal/widget/TransportControlView.java
index 29ad15b..f9c2cce 100644
--- a/core/java/com/android/internal/widget/TransportControlView.java
+++ b/core/java/com/android/internal/widget/TransportControlView.java
@@ -21,6 +21,8 @@
import com.android.internal.widget.LockScreenWidgetCallback;
import com.android.internal.widget.LockScreenWidgetInterface;
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -68,7 +70,7 @@
private int mClientGeneration;
private Metadata mMetadata = new Metadata();
private boolean mAttached;
- private ComponentName mClientName;
+ private PendingIntent mClientIntent;
private int mTransportControlFlags;
private int mPlayState;
private AudioManager mAudioManager;
@@ -116,7 +118,7 @@
}
}
mClientGeneration = msg.arg1;
- mClientName = (ComponentName) msg.obj;
+ mClientIntent = (PendingIntent) msg.obj;
break;
}
@@ -174,12 +176,12 @@
}
}
- public void setCurrentClientId(int clientGeneration, ComponentName clientEventReceiver,
+ public void setCurrentClientId(int clientGeneration, PendingIntent mediaIntent,
boolean clearing) throws RemoteException {
Handler handler = mLocalHandler.get();
if (handler != null) {
handler.obtainMessage(MSG_SET_GENERATION_ID,
- clientGeneration, (clearing ? 1 : 0), clientEventReceiver).sendToTarget();
+ clientGeneration, (clearing ? 1 : 0), mediaIntent).sendToTarget();
}
}
};
@@ -365,16 +367,27 @@
}
private void sendMediaButtonClick(int keyCode) {
- // TODO: target to specific player based on mClientName
+ // use the registered PendingIntent that will be processed by the registered
+ // media button event receiver, which is the component of mClientIntent
KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
- getContext().sendOrderedBroadcast(intent, null);
+ try {
+ mClientIntent.send(getContext(), 0, intent);
+ } catch (CanceledException e) {
+ Log.e(TAG, "Error sending intent for media button down: "+e);
+ e.printStackTrace();
+ }
keyEvent = new KeyEvent(KeyEvent.ACTION_UP, keyCode);
intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
- getContext().sendOrderedBroadcast(intent, null);
+ try {
+ mClientIntent.send(getContext(), 0, intent);
+ } catch (CanceledException e) {
+ Log.e(TAG, "Error sending intent for media button up: "+e);
+ e.printStackTrace();
+ }
}
public void setCallback(LockScreenWidgetCallback callback) {
diff --git a/core/jni/android/graphics/TextLayout.cpp b/core/jni/android/graphics/TextLayout.cpp
index fa9a7b7..a2d6efb 100644
--- a/core/jni/android/graphics/TextLayout.cpp
+++ b/core/jni/android/graphics/TextLayout.cpp
@@ -271,19 +271,11 @@
}
}
-void TextLayout::getTextRunAdvancesHB(SkPaint* paint, const jchar* chars, jint start,
- jint count, jint contextCount, jint dirFlags,
- jfloat* resultAdvances, jfloat& resultTotalAdvance) {
- // Compute advances and return them
- TextLayoutCacheValue::computeValuesWithHarfbuzz(paint, chars, start, count, contextCount,
- dirFlags, resultAdvances, &resultTotalAdvance, NULL, NULL);
-}
-
void TextLayout::getTextRunAdvancesICU(SkPaint* paint, const jchar* chars, jint start,
jint count, jint contextCount, jint dirFlags,
jfloat* resultAdvances, jfloat& resultTotalAdvance) {
// Compute advances and return them
- TextLayoutCacheValue::computeAdvancesWithICU(paint, chars, start, count, contextCount, dirFlags,
+ computeAdvancesWithICU(paint, chars, start, count, contextCount, dirFlags,
resultAdvances, &resultTotalAdvance);
}
@@ -321,4 +313,73 @@
}
}
+void TextLayout::computeAdvancesWithICU(SkPaint* paint, const UChar* chars,
+ size_t start, size_t count, size_t contextCount, int dirFlags,
+ jfloat* outAdvances, jfloat* outTotalAdvance) {
+ SkAutoSTMalloc<CHAR_BUFFER_SIZE, jchar> tempBuffer(contextCount);
+ jchar* buffer = tempBuffer.get();
+ SkScalar* scalarArray = (SkScalar*)outAdvances;
+
+ // this is where we'd call harfbuzz
+ // for now we just use ushape.c
+ size_t widths;
+ const jchar* text;
+ if (dirFlags & 0x1) { // rtl, call arabic shaping in case
+ UErrorCode status = U_ZERO_ERROR;
+ // Use fixed length since we need to keep start and count valid
+ u_shapeArabic(chars, contextCount, buffer, contextCount,
+ U_SHAPE_LENGTH_FIXED_SPACES_NEAR |
+ U_SHAPE_TEXT_DIRECTION_LOGICAL | U_SHAPE_LETTERS_SHAPE |
+ U_SHAPE_X_LAMALEF_SUB_ALTERNATE, &status);
+ // we shouldn't fail unless there's an out of memory condition,
+ // in which case we're hosed anyway
+ for (int i = start, e = i + count; i < e; ++i) {
+ if (buffer[i] == UNICODE_NOT_A_CHAR) {
+ buffer[i] = UNICODE_ZWSP; // zero-width-space for skia
+ }
+ }
+ text = buffer + start;
+ widths = paint->getTextWidths(text, count << 1, scalarArray);
+ } else {
+ text = chars + start;
+ widths = paint->getTextWidths(text, count << 1, scalarArray);
+ }
+
+ jfloat totalAdvance = 0;
+ if (widths < count) {
+#if DEBUG_ADVANCES
+ LOGD("ICU -- count=%d", widths);
+#endif
+ // Skia operates on code points, not code units, so surrogate pairs return only
+ // one value. Expand the result so we have one value per UTF-16 code unit.
+
+ // Note, skia's getTextWidth gets confused if it encounters a surrogate pair,
+ // leaving the remaining widths zero. Not nice.
+ for (size_t i = 0, p = 0; i < widths; ++i) {
+ totalAdvance += outAdvances[p++] = SkScalarToFloat(scalarArray[i]);
+ if (p < count &&
+ text[p] >= UNICODE_FIRST_LOW_SURROGATE &&
+ text[p] < UNICODE_FIRST_PRIVATE_USE &&
+ text[p-1] >= UNICODE_FIRST_HIGH_SURROGATE &&
+ text[p-1] < UNICODE_FIRST_LOW_SURROGATE) {
+ outAdvances[p++] = 0;
+ }
+#if DEBUG_ADVANCES
+ LOGD("icu-adv = %f - total = %f", outAdvances[i], totalAdvance);
+#endif
+ }
+ } else {
+#if DEBUG_ADVANCES
+ LOGD("ICU -- count=%d", count);
+#endif
+ for (size_t i = 0; i < count; i++) {
+ totalAdvance += outAdvances[i] = SkScalarToFloat(scalarArray[i]);
+#if DEBUG_ADVANCES
+ LOGD("icu-adv = %f - total = %f", outAdvances[i], totalAdvance);
+#endif
+ }
+ }
+ *outTotalAdvance = totalAdvance;
+}
+
}
diff --git a/core/jni/android/graphics/TextLayout.h b/core/jni/android/graphics/TextLayout.h
index 0a29d78..9d8913c 100644
--- a/core/jni/android/graphics/TextLayout.h
+++ b/core/jni/android/graphics/TextLayout.h
@@ -77,10 +77,6 @@
jint count, jint contextCount, jint dirFlags,
jfloat* resultAdvances, jfloat& resultTotalAdvance);
- static void getTextRunAdvancesHB(SkPaint* paint, const jchar* chars, jint start,
- jint count, jint contextCount, jint dirFlags,
- jfloat* resultAdvances, jfloat& resultTotalAdvance);
-
static void drawText(SkPaint* paint, const jchar* text, jsize len,
jint bidiFlags, jfloat x, jfloat y, SkCanvas* canvas);
@@ -106,5 +102,9 @@
UErrorCode &status);
static void handleText(SkPaint* paint, const jchar* text, jsize len,
int bidiFlags, jfloat x, jfloat y, SkCanvas* canvas, SkPath* path);
+
+ static void computeAdvancesWithICU(SkPaint* paint, const UChar* chars,
+ size_t start, size_t count, size_t contextCount, int dirFlags,
+ jfloat* outAdvances, jfloat* outTotalAdvance);
};
} // namespace android
diff --git a/core/jni/android/graphics/TextLayoutCache.cpp b/core/jni/android/graphics/TextLayoutCache.cpp
index d04e059..640efad 100644
--- a/core/jni/android/graphics/TextLayoutCache.cpp
+++ b/core/jni/android/graphics/TextLayoutCache.cpp
@@ -52,13 +52,7 @@
mCacheStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
if (mDebugEnabled) {
- LOGD("Start time: %lld", mCacheStartTime);
-#if RTL_USE_HARFBUZZ
- LOGD("Using HARFBUZZ");
-#else
- LOGD("Using ICU");
-#endif
- LOGD("Initialization is done");
+ LOGD("Initialization is done - Start time: %lld", mCacheStartTime);
}
mInitialized = true;
@@ -242,6 +236,22 @@
hinting = paint->getHinting();
}
+TextLayoutCacheKey::TextLayoutCacheKey(const TextLayoutCacheKey& other) :
+ text(NULL),
+ textCopy(other.textCopy),
+ contextCount(other.contextCount),
+ dirFlags(other.dirFlags),
+ typeface(other.typeface),
+ textSize(other.textSize),
+ textSkewX(other.textSkewX),
+ textScaleX(other.textScaleX),
+ flags(other.flags),
+ hinting(other.hinting) {
+ if (other.text) {
+ textCopy.setTo(other.text);
+ }
+}
+
bool TextLayoutCacheKey::operator<(const TextLayoutCacheKey& rhs) const {
LTE_INT(count) {
LTE_INT(contextCount) {
@@ -253,7 +263,7 @@
LTE_INT(flags) {
LTE_INT(hinting) {
LTE_INT(dirFlags) {
- return strncmp16(text, rhs.text, contextCount) < 0;
+ return strncmp16(getText(), rhs.getText(), contextCount) < 0;
}
}
}
@@ -269,7 +279,7 @@
void TextLayoutCacheKey::internalTextCopy() {
textCopy.setTo(text, contextCount);
- text = textCopy.string();
+ text = NULL;
}
size_t TextLayoutCacheKey::getSize() {
@@ -280,13 +290,7 @@
* TextLayoutCacheValue
*/
TextLayoutCacheValue::TextLayoutCacheValue() :
- mAdvances(NULL), mTotalAdvance(0), mAdvancesCount(0),
- mGlyphs(NULL), mGlyphsCount(0), mElapsedTime(0) {
-}
-
-TextLayoutCacheValue::~TextLayoutCacheValue() {
- delete[] mAdvances;
- delete[] mGlyphs;
+ mTotalAdvance(0), mElapsedTime(0) {
}
void TextLayoutCacheValue::setElapsedTime(uint32_t time) {
@@ -299,16 +303,11 @@
void TextLayoutCacheValue::computeValues(SkPaint* paint, const UChar* chars, size_t start,
size_t count, size_t contextCount, int dirFlags) {
- mAdvancesCount = count;
- mAdvances = new float[count];
-
-#if RTL_USE_HARFBUZZ
+ // Give a hint for advances and glyphs vectors size
+ mAdvances.setCapacity(count);
+ mGlyphs.setCapacity(count);
computeValuesWithHarfbuzz(paint, chars, start, count, contextCount, dirFlags,
- mAdvances, &mTotalAdvance, &mGlyphs, &mGlyphsCount);
-#else
- computeAdvancesWithICU(paint, chars, start, count, contextCount, dirFlags,
- mAdvances, &mTotalAdvance);
-#endif
+ &mAdvances, &mTotalAdvance, &mGlyphs);
#if DEBUG_ADVANCES
LOGD("Advances - count=%d - countextCount=%d - totalAdvance=%f - "
"adv[0]=%f adv[1]=%f adv[2]=%f adv[3]=%f", count, contextCount, mTotalAdvance,
@@ -317,8 +316,8 @@
}
size_t TextLayoutCacheValue::getSize() {
- return sizeof(TextLayoutCacheValue) + sizeof(jfloat) * mAdvancesCount +
- sizeof(jchar) * mGlyphsCount;
+ return sizeof(TextLayoutCacheValue) + sizeof(jfloat) * mAdvances.capacity() +
+ sizeof(jchar) * mGlyphs.capacity();
}
void TextLayoutCacheValue::setupShaperItem(HB_ShaperItem* shaperItem, HB_FontRec* font,
@@ -389,27 +388,10 @@
}
}
-struct GlyphRun {
- inline GlyphRun() {}
- inline GlyphRun(jchar* glyphs, size_t glyphsCount, bool isRTL) :
- glyphs(glyphs), glyphsCount(glyphsCount), isRTL(isRTL) { }
- jchar* glyphs;
- size_t glyphsCount;
- int isRTL;
-};
-
-void static reverseGlyphArray(jchar* glyphs, size_t count) {
- for (size_t i = 0; i < count / 2; i++) {
- jchar temp = glyphs[i];
- glyphs[i] = glyphs[count - 1 - i];
- glyphs[count - 1 - i] = temp;
- }
-}
-
void TextLayoutCacheValue::computeValuesWithHarfbuzz(SkPaint* paint, const UChar* chars,
size_t start, size_t count, size_t contextCount, int dirFlags,
- jfloat* outAdvances, jfloat* outTotalAdvance,
- jchar** outGlyphs, size_t* outGlyphsCount) {
+ Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
+ Vector<jchar>* const outGlyphs) {
UBiDiLevel bidiReq = 0;
bool forceLTR = false;
@@ -430,11 +412,7 @@
forceLTR, forceRTL);
#endif
computeRunValuesWithHarfbuzz(paint, chars, start, count, contextCount, forceRTL,
- outAdvances, outTotalAdvance, outGlyphs, outGlyphsCount);
-
- if (forceRTL && *outGlyphsCount > 1) {
- reverseGlyphArray(*outGlyphs, *outGlyphsCount);
- }
+ outAdvances, outTotalAdvance, outGlyphs);
} else {
UBiDi* bidi = ubidi_open();
if (bidi) {
@@ -456,15 +434,8 @@
"-- run-start=%d run-len=%d isRTL=%d", start, count, isRTL);
#endif
computeRunValuesWithHarfbuzz(paint, chars, start, count, contextCount,
- isRTL, outAdvances, outTotalAdvance, outGlyphs, outGlyphsCount);
-
- if (isRTL && *outGlyphsCount > 1) {
- reverseGlyphArray(*outGlyphs, *outGlyphsCount);
- }
+ isRTL, outAdvances, outTotalAdvance, outGlyphs);
} else {
- Vector<GlyphRun> glyphRuns;
- jchar* runGlyphs;
- size_t runGlyphsCount = 0;
int32_t end = start + count;
for (size_t i = 0; i < rc; ++i) {
int32_t startRun;
@@ -498,34 +469,9 @@
computeRunValuesWithHarfbuzz(paint, chars, startRun,
lengthRun, contextCount, isRTL,
outAdvances, &runTotalAdvance,
- &runGlyphs, &runGlyphsCount);
+ outGlyphs);
- outAdvances += lengthRun;
*outTotalAdvance += runTotalAdvance;
- *outGlyphsCount += runGlyphsCount;
-#if DEBUG_GLYPHS
- LOGD("computeValuesWithHarfbuzz -- run=%d run-glyphs-count=%d",
- i, runGlyphsCount);
- for (size_t j = 0; j < runGlyphsCount; j++) {
- LOGD(" -- glyphs[%d]=%d", j, runGlyphs[j]);
- }
-#endif
- glyphRuns.push(GlyphRun(runGlyphs, runGlyphsCount, isRTL));
- }
- *outGlyphs = new jchar[*outGlyphsCount];
-
- jchar* glyphs = *outGlyphs;
- for (size_t i = 0; i < glyphRuns.size(); i++) {
- const GlyphRun& glyphRun = glyphRuns.itemAt(i);
- if (glyphRun.isRTL) {
- for (size_t n = 0; n < glyphRun.glyphsCount; n++) {
- glyphs[glyphRun.glyphsCount - n - 1] = glyphRun.glyphs[n];
- }
- } else {
- memcpy(glyphs, glyphRun.glyphs, glyphRun.glyphsCount * sizeof(jchar));
- }
- glyphs += glyphRun.glyphsCount;
- delete[] glyphRun.glyphs;
}
}
}
@@ -538,22 +484,18 @@
"-- run-start=%d run-len=%d isRTL=%d", start, count, isRTL);
#endif
computeRunValuesWithHarfbuzz(paint, chars, start, count, contextCount, isRTL,
- outAdvances, outTotalAdvance, outGlyphs, outGlyphsCount);
-
- if (isRTL && *outGlyphsCount > 1) {
- reverseGlyphArray(*outGlyphs, *outGlyphsCount);
- }
+ outAdvances, outTotalAdvance, outGlyphs);
}
}
#if DEBUG_GLYPHS
- LOGD("computeValuesWithHarfbuzz -- total-glyphs-count=%d", *outGlyphsCount);
+ LOGD("computeValuesWithHarfbuzz -- total-glyphs-count=%d", outGlyphs->size());
#endif
}
static void logGlyphs(HB_ShaperItem shaperItem) {
LOGD("Got glyphs - count=%d", shaperItem.num_glyphs);
for (size_t i = 0; i < shaperItem.num_glyphs; i++) {
- LOGD(" glyphs[%d]=%d - offset.x=%f offset.y=%f", i, shaperItem.glyphs[i],
+ LOGD(" glyph[%d]=%d - offset.x=%f offset.y=%f", i, shaperItem.glyphs[i],
HBFixedToFloat(shaperItem.offsets[i].x),
HBFixedToFloat(shaperItem.offsets[i].y));
}
@@ -561,12 +503,13 @@
void TextLayoutCacheValue::computeRunValuesWithHarfbuzz(SkPaint* paint, const UChar* chars,
size_t start, size_t count, size_t contextCount, bool isRTL,
- jfloat* outAdvances, jfloat* outTotalAdvance,
- jchar** outGlyphs, size_t* outGlyphsCount) {
+ Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
+ Vector<jchar>* const outGlyphs) {
HB_ShaperItem shaperItem;
HB_FontRec font;
FontData fontData;
+
shapeWithHarfbuzz(&shaperItem, &font, &fontData, paint, chars, start, count,
contextCount, isRTL);
@@ -583,30 +526,28 @@
#if DEBUG_GLYPHS
LOGD("HARFBUZZ -- advances array is empty or num_glypth = 0");
#endif
- for (size_t i = 0; i < count; i++) {
- outAdvances[i] = 0;
- }
+ outAdvances->insertAt(0, outAdvances->size(), count);
*outTotalAdvance = 0;
- if (outGlyphs) {
- *outGlyphsCount = 0;
- *outGlyphs = new jchar[0];
- }
-
// Cleaning
deleteGlyphArrays(&shaperItem);
HB_FreeFace(shaperItem.face);
return;
}
+
// Get Advances and their total
- jfloat totalAdvance = outAdvances[0] = HBFixedToFloat(shaperItem.advances[shaperItem.log_clusters[0]]);
+ jfloat currentAdvance = HBFixedToFloat(shaperItem.advances[shaperItem.log_clusters[0]]);
+ jfloat totalAdvance = currentAdvance;
+ outAdvances->add(currentAdvance);
for (size_t i = 1; i < count; i++) {
size_t clusterPrevious = shaperItem.log_clusters[i - 1];
size_t cluster = shaperItem.log_clusters[i];
if (cluster == clusterPrevious) {
- outAdvances[i] = 0;
+ outAdvances->add(0);
} else {
- totalAdvance += outAdvances[i] = HBFixedToFloat(shaperItem.advances[shaperItem.log_clusters[i]]);
+ currentAdvance = HBFixedToFloat(shaperItem.advances[shaperItem.log_clusters[i]]);
+ totalAdvance += currentAdvance;
+ outAdvances->add(currentAdvance);
}
}
*outTotalAdvance = totalAdvance;
@@ -618,12 +559,15 @@
}
#endif
- // Get Glyphs
+ // Get Glyphs and reverse them in place if RTL
if (outGlyphs) {
- *outGlyphsCount = shaperItem.num_glyphs;
- *outGlyphs = new jchar[shaperItem.num_glyphs];
- for (size_t i = 0; i < shaperItem.num_glyphs; i++) {
- (*outGlyphs)[i] = (jchar) shaperItem.glyphs[i];
+ size_t count = shaperItem.num_glyphs;
+ for (size_t i = 0; i < count; i++) {
+ jchar glyph = (jchar) shaperItem.glyphs[(!isRTL) ? i : count - 1 - i];
+#if DEBUG_GLYPHS
+ LOGD("HARFBUZZ -- glyph[%d]=%d", i, glyph);
+#endif
+ outGlyphs->add(glyph);
}
}
@@ -632,75 +576,6 @@
HB_FreeFace(shaperItem.face);
}
-void TextLayoutCacheValue::computeAdvancesWithICU(SkPaint* paint, const UChar* chars,
- size_t start, size_t count, size_t contextCount, int dirFlags,
- jfloat* outAdvances, jfloat* outTotalAdvance) {
- SkAutoSTMalloc<CHAR_BUFFER_SIZE, jchar> tempBuffer(contextCount);
- jchar* buffer = tempBuffer.get();
- SkScalar* scalarArray = (SkScalar*)outAdvances;
-
- // this is where we'd call harfbuzz
- // for now we just use ushape.c
- size_t widths;
- const jchar* text;
- if (dirFlags & 0x1) { // rtl, call arabic shaping in case
- UErrorCode status = U_ZERO_ERROR;
- // Use fixed length since we need to keep start and count valid
- u_shapeArabic(chars, contextCount, buffer, contextCount,
- U_SHAPE_LENGTH_FIXED_SPACES_NEAR |
- U_SHAPE_TEXT_DIRECTION_LOGICAL | U_SHAPE_LETTERS_SHAPE |
- U_SHAPE_X_LAMALEF_SUB_ALTERNATE, &status);
- // we shouldn't fail unless there's an out of memory condition,
- // in which case we're hosed anyway
- for (int i = start, e = i + count; i < e; ++i) {
- if (buffer[i] == UNICODE_NOT_A_CHAR) {
- buffer[i] = UNICODE_ZWSP; // zero-width-space for skia
- }
- }
- text = buffer + start;
- widths = paint->getTextWidths(text, count << 1, scalarArray);
- } else {
- text = chars + start;
- widths = paint->getTextWidths(text, count << 1, scalarArray);
- }
-
- jfloat totalAdvance = 0;
- if (widths < count) {
-#if DEBUG_ADVANCES
- LOGD("ICU -- count=%d", widths);
-#endif
- // Skia operates on code points, not code units, so surrogate pairs return only
- // one value. Expand the result so we have one value per UTF-16 code unit.
-
- // Note, skia's getTextWidth gets confused if it encounters a surrogate pair,
- // leaving the remaining widths zero. Not nice.
- for (size_t i = 0, p = 0; i < widths; ++i) {
- totalAdvance += outAdvances[p++] = SkScalarToFloat(scalarArray[i]);
- if (p < count &&
- text[p] >= UNICODE_FIRST_LOW_SURROGATE &&
- text[p] < UNICODE_FIRST_PRIVATE_USE &&
- text[p-1] >= UNICODE_FIRST_HIGH_SURROGATE &&
- text[p-1] < UNICODE_FIRST_LOW_SURROGATE) {
- outAdvances[p++] = 0;
- }
-#if DEBUG_ADVANCES
- LOGD("icu-adv = %f - total = %f", outAdvances[i], totalAdvance);
-#endif
- }
- } else {
-#if DEBUG_ADVANCES
- LOGD("ICU -- count=%d", count);
-#endif
- for (size_t i = 0; i < count; i++) {
- totalAdvance += outAdvances[i] = SkScalarToFloat(scalarArray[i]);
-#if DEBUG_ADVANCES
- LOGD("icu-adv = %f - total = %f", outAdvances[i], totalAdvance);
-#endif
- }
- }
- *outTotalAdvance = totalAdvance;
-}
-
void TextLayoutCacheValue::deleteGlyphArrays(HB_ShaperItem* shaperItem) {
delete[] shaperItem->glyphs;
delete[] shaperItem->attributes;
diff --git a/core/jni/android/graphics/TextLayoutCache.h b/core/jni/android/graphics/TextLayoutCache.h
index 0d8d71f..1f08bda 100644
--- a/core/jni/android/graphics/TextLayoutCache.h
+++ b/core/jni/android/graphics/TextLayoutCache.h
@@ -72,6 +72,8 @@
const UChar* text, size_t start, size_t count,
size_t contextCount, int dirFlags);
+ TextLayoutCacheKey(const TextLayoutCacheKey& other);
+
bool operator<(const TextLayoutCacheKey& rhs) const;
/**
@@ -86,7 +88,7 @@
size_t getSize();
private:
- const UChar* text;
+ const UChar* text; // if text is NULL, use textCopy
String16 textCopy;
size_t start;
size_t count;
@@ -98,15 +100,16 @@
SkScalar textScaleX;
uint32_t flags;
SkPaint::Hinting hinting;
+
+ inline const UChar* getText() const {
+ return text ? text : textCopy.string();
+ }
}; // TextLayoutCacheKey
/*
* TextLayoutCacheValue is the Cache value
*/
class TextLayoutCacheValue : public RefBase {
-protected:
- ~TextLayoutCacheValue();
-
public:
TextLayoutCacheValue();
@@ -116,11 +119,11 @@
void computeValues(SkPaint* paint, const UChar* chars, size_t start, size_t count,
size_t contextCount, int dirFlags);
- inline const jfloat* getAdvances() const { return mAdvances; }
- inline size_t getAdvancesCount() const { return mAdvancesCount; }
+ inline const jfloat* getAdvances() const { return mAdvances.array(); }
+ inline size_t getAdvancesCount() const { return mAdvances.size(); }
inline jfloat getTotalAdvance() const { return mTotalAdvance; }
- inline const jchar* getGlyphs() const { return mGlyphs; }
- inline size_t getGlyphsCount() const { return mGlyphsCount; }
+ inline const jchar* getGlyphs() const { return mGlyphs.array(); }
+ inline size_t getGlyphsCount() const { return mGlyphs.size(); }
/**
* Get the size of the Cache entry
@@ -137,8 +140,8 @@
static void computeValuesWithHarfbuzz(SkPaint* paint, const UChar* chars, size_t start,
size_t count, size_t contextCount, int dirFlags,
- jfloat* outAdvances, jfloat* outTotalAdvance,
- jchar** outGlyphs, size_t* outGlyphsCount);
+ Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
+ Vector<jchar>* const outGlyphs);
static void computeAdvancesWithICU(SkPaint* paint, const UChar* chars, size_t start,
size_t count, size_t contextCount, int dirFlags,
@@ -146,9 +149,9 @@
private:
/**
- * Advances array
+ * Advances vector
*/
- jfloat* mAdvances;
+ Vector<jfloat> mAdvances;
/**
* Total number of advances
@@ -156,19 +159,9 @@
jfloat mTotalAdvance;
/**
- * Allocated size for advances array
+ * Glyphs vector
*/
- size_t mAdvancesCount;
-
- /**
- * Glyphs array
- */
- jchar* mGlyphs;
-
- /**
- * Total number of glyphs
- */
- size_t mGlyphsCount;
+ Vector<jchar> mGlyphs;
/**
* Time for computing the values (in milliseconds)
@@ -181,8 +174,8 @@
static void computeRunValuesWithHarfbuzz(SkPaint* paint, const UChar* chars, size_t start,
size_t count, size_t contextCount, bool isRTL,
- jfloat* outAdvances, jfloat* outTotalAdvance,
- jchar** outGlyphs, size_t* outGlyphsCount);
+ Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
+ Vector<jchar>* const outGlyphs);
}; // TextLayoutCacheValue
/**
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 80741eb..9755f22 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1533,19 +1533,14 @@
<activity android:name="android.accounts.ChooseTypeAndAccountActivity"
android:excludeFromRecents="true"
android:exported="true"
- android:theme="@android:style/Theme.Holo.Dialog"
+ android:theme="@android:style/Theme.Holo.DialogWhenLarge.NoActionBar"
android:label="@string/choose_account_label"
android:process=":ui">
- <intent-filter>
- <action android:name="android.intent.action.PICK" />
- <category android:name="android.intent.category.ACCOUNT" />
- </intent-filter>
</activity>
<activity android:name="android.accounts.ChooseAccountTypeActivity"
android:excludeFromRecents="true"
- android:exported="true"
- android:theme="@android:style/Theme.Holo.Dialog"
+ android:theme="@android:style/Theme.Holo.DialogWhenLarge.NoActionBar"
android:label="@string/choose_account_label"
android:process=":ui">
</activity>
diff --git a/core/res/res/drawable-hdpi/ic_checkmark_holo_light.png b/core/res/res/drawable-hdpi/ic_checkmark_holo_light.png
new file mode 100644
index 0000000..2c6719b
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_checkmark_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_checkmark_holo_light.png b/core/res/res/drawable-mdpi/ic_checkmark_holo_light.png
new file mode 100644
index 0000000..744e964
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_checkmark_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_checkmark_holo_light.png b/core/res/res/drawable-xhdpi/ic_checkmark_holo_light.png
new file mode 100644
index 0000000..1607f35
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_checkmark_holo_light.png
Binary files differ
diff --git a/core/res/res/layout/choose_account_type.xml b/core/res/res/layout/choose_account_type.xml
new file mode 100644
index 0000000..db96dc1
--- /dev/null
+++ b/core/res/res/layout/choose_account_type.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/layout/list_content.xml
+**
+** Copyright 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.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:paddingLeft="16dip"
+ android:paddingRight="16dip">
+
+ <TextView android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_gravity="left"
+ android:text="@string/add_account_label"
+ android:paddingTop="16dip"
+ android:paddingBottom="16dip"
+ android:textColor="@android:color/holo_blue_light"
+ />
+
+ <View android:layout_height="3dip"
+ android:layout_width="match_parent"
+ android:background="#323232"/>
+
+ <ListView android:id="@android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:drawSelectorOnTop="false"
+ android:scrollbarAlwaysDrawVerticalTrack="true" />
+
+</LinearLayout>
diff --git a/core/res/res/layout/choose_selected_account_row.xml b/core/res/res/layout/choose_selected_account_row.xml
new file mode 100644
index 0000000..d88750d
--- /dev/null
+++ b/core/res/res/layout/choose_selected_account_row.xml
@@ -0,0 +1,46 @@
+<?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.
+ */
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:paddingLeft="0dip"
+ android:paddingRight="0dip"
+ android:orientation="horizontal" >
+
+ <ImageView android:id="@+id/account_row_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"
+ android:paddingRight="8dip" />
+
+ <TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/account_row_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:gravity="center_vertical"
+ android:minHeight="?android:attr/listPreferredItemHeight" />
+
+ <ImageView android:id="@+id/account_row_checkmark"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="right"
+ android:layout_weight="2"
+ android:paddingRight="8dip"
+ android:src="@drawable/ic_checkmark_holo_light" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/core/res/res/layout/choose_type_and_account.xml b/core/res/res/layout/choose_type_and_account.xml
index 8be01b42..5a05126 100644
--- a/core/res/res/layout/choose_type_and_account.xml
+++ b/core/res/res/layout/choose_type_and_account.xml
@@ -24,21 +24,44 @@
android:paddingLeft="16dip"
android:paddingRight="16dip">
- <ListView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@android:id/list"
+ <TextView android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_gravity="left"
+ android:text="@string/choose_account_label"
+ android:paddingTop="16dip"
+ android:paddingBottom="16dip"
+ android:textColor="@android:color/holo_blue_light"
+ />
+
+ <View android:layout_height="3dip"
+ android:layout_width="match_parent"
+ android:background="#323232"/>
+
+ <TextView android:id="@+id/description"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_gravity="left|center_vertical"
+ android:text="@string/choose_account_text"
+ android:paddingTop="16dip"
+ android:paddingBottom="16dip"
+ />
+
+ <ListView android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_weight="1"
android:drawSelectorOnTop="false"
+ android:layout_weight="1"
android:scrollbarAlwaysDrawVerticalTrack="true" />
<Button android:id="@+id/addAccount"
- android:layout_width="wrap_content"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_weight="2"
android:layout_marginLeft="2dip"
android:layout_marginRight="2dip"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:textStyle="bold"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:text="@string/add_account_button_label"
/>
</LinearLayout>
diff --git a/core/res/res/layout/volume_adjust.xml b/core/res/res/layout/volume_adjust.xml
index ea4e1f9..c16a12c 100644
--- a/core/res/res/layout/volume_adjust.xml
+++ b/core/res/res/layout/volume_adjust.xml
@@ -21,7 +21,6 @@
android:id="@+id/visible_panel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="80dp"
android:background="@android:drawable/dialog_full_holo_dark"
android:orientation="horizontal"
>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index f1fc42c..0d15388 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -178,4 +178,6 @@
<!-- Default width for a textview error popup -->
<dimen name="textview_error_popup_default_width">240dip</dimen>
+ <!-- Volume panel y offset -->
+ <dimen name="volume_panel_top">80dp</dimen>
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index f75c7d5..e093fa9 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3094,6 +3094,12 @@
<!-- Choose Account Activity label -->
<string name="choose_account_label">Select an account</string>
+ <string name="add_account_label">"Add an account"</string>
+ <string name="choose_account_text">"Which account would you like to use?"</string>
+
+ <!-- Button label to add an account [CHAR LIMIT=20] -->
+ <string name="add_account_button_label">Add account</string>
+
<!-- NumberPicker - accessibility support -->
<!-- Description of the button to increment the NumberPicker value. [CHAR LIMIT=NONE] -->
<string name="number_picker_increment_button">Increment</string>
@@ -3174,6 +3180,9 @@
<!-- Slide lock screen -->
+ <!-- Description of the sliding handle in the Slide unlock screen. [CHAR LIMIT=NONE] -->
+ <string name="content_description_sliding_handle">"Sliding handle. Tap and hold."</string>
+
<!-- Description of the up direction in which one can to slide the handle in the Slide unlock screen. [CHAR LIMIT=NONE] -->
<string name="description_direction_up">Up for <xliff:g id="target_description" example="Unlock">%s</xliff:g>.</string>
<!-- Description of the down direction in which one can to slide the handle in the Slide unlock screen. [CHAR LIMIT=NONE] -->
@@ -3293,4 +3302,4 @@
<!-- Delimeter used between each item in a textual list; for example "Alpha, Beta". [CHAR LIMIT=3] -->
<string name="list_delimeter">", "</string>
-</resources>
+</resources>
\ No newline at end of file
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 346a3d29..88b45fd 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -250,7 +250,7 @@
</style>
<style name="TextAppearance.EasyCorrectSuggestion" parent="TextAppearance.Suggestion">
- <item name="android:textUnderlineColor">@color/holo_blue_dark</item>
+ <item name="android:textUnderlineColor">#ff888888</item>
</style>
<style name="TextAppearance.MisspelledSuggestion" parent="TextAppearance.Suggestion">
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 1089969..1987d99 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -94,7 +94,7 @@
<item name="textAppearanceButton">@android:style/TextAppearance.Widget.Button</item>
- <item name="editTextColor">?android:color/primary_text_light</item>
+ <item name="editTextColor">@android:color/primary_text_light</item>
<item name="editTextBackground">@android:drawable/edit_text</item>
<item name="candidatesTextStyleSpans">@android:string/candidates_style</item>
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index cd8bb1d..a0881a7 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -18,8 +18,10 @@
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.os.Binder;
@@ -1684,17 +1686,42 @@
* Register a component to be the sole receiver of MEDIA_BUTTON intents.
* @param eventReceiver identifier of a {@link android.content.BroadcastReceiver}
* that will receive the media button intent. This broadcast receiver must be declared
- * in the application manifest.
+ * in the application manifest. The package of the component must match that of
+ * the context you're registering from.
*/
public void registerMediaButtonEventReceiver(ComponentName eventReceiver) {
if (eventReceiver == null) {
return;
}
+ if (!eventReceiver.getPackageName().equals(mContext.getPackageName())) {
+ Log.e(TAG, "registerMediaButtonEventReceiver() error: " +
+ "receiver and context package names don't match");
+ return;
+ }
+ // construct a PendingIntent for the media button and register it
+ Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+ // the associated intent will be handled by the component being registered
+ mediaButtonIntent.setComponent(eventReceiver);
+ PendingIntent pi = PendingIntent.getBroadcast(mContext,
+ 0/*requestCode, ignored*/, mediaButtonIntent, 0/*flags*/);
+ registerMediaButtonIntent(pi, eventReceiver);
+ }
+
+ /**
+ * @hide
+ * no-op if (pi == null) or (eventReceiver == null)
+ */
+ public void registerMediaButtonIntent(PendingIntent pi, ComponentName eventReceiver) {
+ if ((pi == null) || (eventReceiver == null)) {
+ Log.e(TAG, "Cannot call registerMediaButtonIntent() with a null parameter");
+ return;
+ }
IAudioService service = getService();
try {
- service.registerMediaButtonEventReceiver(eventReceiver);
+ // pi != null
+ service.registerMediaButtonIntent(pi, eventReceiver);
} catch (RemoteException e) {
- Log.e(TAG, "Dead object in registerMediaButtonEventReceiver"+e);
+ Log.e(TAG, "Dead object in registerMediaButtonIntent"+e);
}
}
@@ -1707,14 +1734,26 @@
if (eventReceiver == null) {
return;
}
- IAudioService service = getService();
- try {
- service.unregisterMediaButtonEventReceiver(eventReceiver);
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in unregisterMediaButtonEventReceiver"+e);
- }
+ // construct a PendingIntent for the media button and unregister it
+ Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+ // the associated intent will be handled by the component being registered
+ mediaButtonIntent.setComponent(eventReceiver);
+ PendingIntent pi = PendingIntent.getBroadcast(mContext,
+ 0/*requestCode, ignored*/, mediaButtonIntent, 0/*flags*/);
+ unregisterMediaButtonIntent(pi, eventReceiver);
}
+ /**
+ * @hide
+ */
+ public void unregisterMediaButtonIntent(PendingIntent pi, ComponentName eventReceiver) {
+ IAudioService service = getService();
+ try {
+ service.unregisterMediaButtonIntent(pi, eventReceiver);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in unregisterMediaButtonIntent"+e);
+ }
+ }
/**
* Registers the remote control client for providing information to display on the remote
@@ -1724,14 +1763,13 @@
* @see RemoteControlClient
*/
public void registerRemoteControlClient(RemoteControlClient rcClient) {
- if ((rcClient == null) || (rcClient.getRcEventReceiver() == null)) {
+ if ((rcClient == null) || (rcClient.getRcMediaIntent() == null)) {
return;
}
IAudioService service = getService();
try {
- service.registerRemoteControlClient(rcClient.getRcEventReceiver(), /* eventReceiver */
+ service.registerRemoteControlClient(rcClient.getRcMediaIntent(), /* mediaIntent */
rcClient.getIRemoteControlClient(), /* rcClient */
- rcClient.toString(), /* clientName */
// used to match media button event receiver and audio focus
mContext.getPackageName()); /* packageName */
} catch (RemoteException e) {
@@ -1746,13 +1784,13 @@
* @see #registerRemoteControlClient(RemoteControlClient)
*/
public void unregisterRemoteControlClient(RemoteControlClient rcClient) {
- if ((rcClient == null) || (rcClient.getRcEventReceiver() == null)) {
+ if ((rcClient == null) || (rcClient.getRcMediaIntent() == null)) {
return;
}
IAudioService service = getService();
try {
- service.unregisterRemoteControlClient(rcClient.getRcEventReceiver(), /* eventReceiver */
- rcClient.getIRemoteControlClient()); /* rcClient */
+ service.unregisterRemoteControlClient(rcClient.getRcMediaIntent(), /* mediaIntent */
+ rcClient.getIRemoteControlClient()); /* rcClient */
} catch (RemoteException e) {
Log.e(TAG, "Dead object in unregisterRemoteControlClient"+e);
}
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index db27cfd..8895c9e 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -18,6 +18,8 @@
import android.app.ActivityManagerNative;
import android.app.KeyguardManager;
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
@@ -35,6 +37,7 @@
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
import android.os.Binder;
+import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
@@ -2084,7 +2087,7 @@
}
}
- private void persistMediaButtonReceiver(ComponentName receiver) {
+ private void onHandlePersistMediaButtonReceiver(ComponentName receiver) {
Settings.System.putString(mContentResolver, Settings.System.MEDIA_BUTTON_RECEIVER,
receiver == null ? "" : receiver.flattenToString());
}
@@ -2201,7 +2204,7 @@
break;
case MSG_PERSIST_MEDIABUTTONRECEIVER:
- persistMediaButtonReceiver( (ComponentName) msg.obj );
+ onHandlePersistMediaButtonReceiver( (ComponentName) msg.obj );
break;
case MSG_RCDISPLAY_CLEAR:
@@ -2894,14 +2897,22 @@
}
synchronized(mRCStack) {
if (!mRCStack.empty()) {
- // create a new intent specifically aimed at the current registered listener
+ // create a new intent to fill in the extras of the registered PendingIntent
Intent targetedIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
- targetedIntent.putExtras(intent.getExtras());
- targetedIntent.setComponent(mRCStack.peek().mReceiverComponent);
- // trap the current broadcast
- abortBroadcast();
- //Log.v(TAG, " Sending intent" + targetedIntent);
- context.sendBroadcast(targetedIntent, null);
+ Bundle extras = intent.getExtras();
+ if (extras != null) {
+ targetedIntent.putExtras(extras);
+ // trap the current broadcast
+ abortBroadcast();
+ //Log.v(TAG, " Sending intent" + targetedIntent);
+ // send the intent that was registered by the client
+ try {
+ mRCStack.peek().mMediaIntent.send(context, 0, targetedIntent);
+ } catch (CanceledException e) {
+ Log.e(TAG, "Error sending pending intent " + mRCStack.peek());
+ e.printStackTrace();
+ }
+ }
}
}
}
@@ -2936,18 +2947,18 @@
*/
private class RcClientDeathHandler implements IBinder.DeathRecipient {
private IBinder mCb; // To be notified of client's death
- private ComponentName mRcEventReceiver;
+ private PendingIntent mMediaIntent;
- RcClientDeathHandler(IBinder cb, ComponentName eventReceiver) {
+ RcClientDeathHandler(IBinder cb, PendingIntent pi) {
mCb = cb;
- mRcEventReceiver = eventReceiver;
+ mMediaIntent = pi;
}
public void binderDied() {
Log.w(TAG, " RemoteControlClient died");
// remote control client died, make sure the displays don't use it anymore
// by setting its remote control client to null
- registerRemoteControlClient(mRcEventReceiver, null, null, null/*ignored*/);
+ registerRemoteControlClient(mMediaIntent, null, null/*ignored*/);
}
public IBinder getBinder() {
@@ -2956,18 +2967,29 @@
}
private static class RemoteControlStackEntry {
- /** the target for the ACTION_MEDIA_BUTTON events */
- public ComponentName mReceiverComponent;// always non null
+ /**
+ * The target for the ACTION_MEDIA_BUTTON events.
+ * Always non null.
+ */
+ public PendingIntent mMediaIntent;
+ /**
+ * The registered media button event receiver.
+ * Always non null.
+ */
+ public ComponentName mReceiverComponent;
public String mCallingPackageName;
- public String mRcClientName;
public int mCallingUid;
-
- /** provides access to the information to display on the remote control */
+ /**
+ * Provides access to the information to display on the remote control.
+ * May be null (when a media button event receiver is registered,
+ * but no remote control client has been registered) */
public IRemoteControlClient mRcClient;
public RcClientDeathHandler mRcClientDeathHandler;
- public RemoteControlStackEntry(ComponentName r) {
- mReceiverComponent = r;
+ /** precondition: mediaIntent != null, eventReceiver != null */
+ public RemoteControlStackEntry(PendingIntent mediaIntent, ComponentName eventReceiver) {
+ mMediaIntent = mediaIntent;
+ mReceiverComponent = eventReceiver;
mCallingUid = -1;
mRcClient = null;
}
@@ -3003,7 +3025,8 @@
Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
while(stackIterator.hasNext()) {
RemoteControlStackEntry rcse = stackIterator.next();
- pw.println(" receiver: " + rcse.mReceiverComponent +
+ pw.println(" pi: " + rcse.mMediaIntent +
+ " -- ercvr: " + rcse.mReceiverComponent +
" -- client: " + rcse.mRcClient +
" -- uid: " + rcse.mCallingUid);
}
@@ -3035,7 +3058,6 @@
mAudioHandler.sendMessage(
mAudioHandler.obtainMessage(MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0,
null));
- return;
} else if (oldTop != mRCStack.peek()) {
// the top of the stack has changed, save it in the system settings
// by posting a message to persist it
@@ -3049,25 +3071,32 @@
/**
* Helper function:
- * Restore remote control receiver from the system settings
+ * Restore remote control receiver from the system settings.
*/
private void restoreMediaButtonReceiver() {
String receiverName = Settings.System.getString(mContentResolver,
Settings.System.MEDIA_BUTTON_RECEIVER);
if ((null != receiverName) && !receiverName.isEmpty()) {
- ComponentName receiverComponentName = ComponentName.unflattenFromString(receiverName);
- registerMediaButtonEventReceiver(receiverComponentName);
+ ComponentName eventReceiver = ComponentName.unflattenFromString(receiverName);
+ // construct a PendingIntent targeted to the restored component name
+ // for the media button and register it
+ Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+ // the associated intent will be handled by the component being registered
+ mediaButtonIntent.setComponent(eventReceiver);
+ PendingIntent pi = PendingIntent.getBroadcast(mContext,
+ 0/*requestCode, ignored*/, mediaButtonIntent, 0/*flags*/);
+ registerMediaButtonIntent(pi, eventReceiver);
}
- // upon restoring (e.g. after boot), do we want to refresh all remotes?
}
/**
* Helper function:
- * Set the new remote control receiver at the top of the RC focus stack
+ * Set the new remote control receiver at the top of the RC focus stack.
+ * precondition: mediaIntent != null, target != null
*/
- private void pushMediaButtonReceiver(ComponentName newReceiver) {
+ private void pushMediaButtonReceiver(PendingIntent mediaIntent, ComponentName target) {
// already at top of stack?
- if (!mRCStack.empty() && mRCStack.peek().mReceiverComponent.equals(newReceiver)) {
+ if (!mRCStack.empty() && mRCStack.peek().mMediaIntent.equals(mediaIntent)) {
return;
}
Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
@@ -3075,31 +3104,32 @@
boolean wasInsideStack = false;
while(stackIterator.hasNext()) {
rcse = (RemoteControlStackEntry)stackIterator.next();
- if(rcse.mReceiverComponent.equals(newReceiver)) {
+ if(rcse.mMediaIntent.equals(mediaIntent)) {
wasInsideStack = true;
stackIterator.remove();
break;
}
}
if (!wasInsideStack) {
- rcse = new RemoteControlStackEntry(newReceiver);
+ rcse = new RemoteControlStackEntry(mediaIntent, target);
}
mRCStack.push(rcse);
// post message to persist the default media button receiver
mAudioHandler.sendMessage( mAudioHandler.obtainMessage(
- MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0, newReceiver/*obj*/) );
+ MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0, target/*obj*/) );
}
/**
* Helper function:
- * Remove the remote control receiver from the RC focus stack
+ * Remove the remote control receiver from the RC focus stack.
+ * precondition: pi != null
*/
- private void removeMediaButtonReceiver(ComponentName newReceiver) {
+ private void removeMediaButtonReceiver(PendingIntent pi) {
Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
while(stackIterator.hasNext()) {
RemoteControlStackEntry rcse = (RemoteControlStackEntry)stackIterator.next();
- if(rcse.mReceiverComponent.equals(newReceiver)) {
+ if(rcse.mMediaIntent.equals(pi)) {
stackIterator.remove();
break;
}
@@ -3110,8 +3140,8 @@
* Helper function:
* Called synchronized on mRCStack
*/
- private boolean isCurrentRcController(ComponentName eventReceiver) {
- if (!mRCStack.empty() && mRCStack.peek().mReceiverComponent.equals(eventReceiver)) {
+ private boolean isCurrentRcController(PendingIntent pi) {
+ if (!mRCStack.empty() && mRCStack.peek().mMediaIntent.equals(pi)) {
return true;
}
return false;
@@ -3124,12 +3154,12 @@
* Update the remote control displays with the new "focused" client generation
*/
private void setNewRcClientOnDisplays_syncRcsCurrc(int newClientGeneration,
- ComponentName newClientEventReceiver, boolean clearing) {
+ PendingIntent newMediaIntent, boolean clearing) {
// NOTE: Only one IRemoteControlDisplay supported in this implementation
if (mRcDisplay != null) {
try {
mRcDisplay.setCurrentClientId(
- newClientGeneration, newClientEventReceiver, clearing);
+ newClientGeneration, newMediaIntent, clearing);
} catch (RemoteException e) {
Log.e(TAG, "Dead display in setNewRcClientOnDisplays_syncRcsCurrc() "+e);
// if we had a display before, stop monitoring its death
@@ -3167,10 +3197,9 @@
* where the display should be cleared.
*/
private void setNewRcClient_syncRcsCurrc(int newClientGeneration,
- ComponentName newClientEventReceiver, boolean clearing) {
+ PendingIntent newMediaIntent, boolean clearing) {
// send the new valid client generation ID to all displays
- setNewRcClientOnDisplays_syncRcsCurrc(newClientGeneration, newClientEventReceiver,
- clearing);
+ setNewRcClientOnDisplays_syncRcsCurrc(newClientGeneration, newMediaIntent, clearing);
// send the new valid client generation ID to all clients
setNewRcClientGenerationOnClients_syncRcsCurrc(newClientGeneration);
}
@@ -3186,7 +3215,7 @@
mCurrentRcClientGen++;
// synchronously update the displays and clients with the new client generation
setNewRcClient_syncRcsCurrc(mCurrentRcClientGen,
- null /*event receiver*/, true /*clearing*/);
+ null /*newMediaIntent*/, true /*clearing*/);
}
}
}
@@ -3204,7 +3233,7 @@
// synchronously update the displays and clients with
// the new client generation
setNewRcClient_syncRcsCurrc(mCurrentRcClientGen,
- rcse.mReceiverComponent /*event receiver*/,
+ rcse.mMediaIntent /*newMediaIntent*/,
false /*clearing*/);
// tell the current client that it needs to send info
@@ -3301,27 +3330,34 @@
updateRemoteControlDisplay_syncAfRcs(infoChangedFlags);
}
- /** see AudioManager.registerMediaButtonEventReceiver(ComponentName eventReceiver) */
- public void registerMediaButtonEventReceiver(ComponentName eventReceiver) {
- Log.i(TAG, " Remote Control registerMediaButtonEventReceiver() for " + eventReceiver);
+ /**
+ * see AudioManager.registerMediaButtonIntent(PendingIntent pi, ComponentName c)
+ * precondition: mediaIntent != null, target != null
+ */
+ public void registerMediaButtonIntent(PendingIntent mediaIntent, ComponentName eventReceiver) {
+ Log.i(TAG, " Remote Control registerMediaButtonIntent() for " + mediaIntent);
synchronized(mAudioFocusLock) {
synchronized(mRCStack) {
- pushMediaButtonReceiver(eventReceiver);
+ pushMediaButtonReceiver(mediaIntent, eventReceiver);
// new RC client, assume every type of information shall be queried
checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL);
}
}
}
- /** see AudioManager.unregisterMediaButtonEventReceiver(ComponentName eventReceiver) */
- public void unregisterMediaButtonEventReceiver(ComponentName eventReceiver) {
- Log.i(TAG, " Remote Control unregisterMediaButtonEventReceiver() for " + eventReceiver);
+ /**
+ * see AudioManager.unregisterMediaButtonIntent(PendingIntent mediaIntent)
+ * precondition: mediaIntent != null, eventReceiver != null
+ */
+ public void unregisterMediaButtonIntent(PendingIntent mediaIntent, ComponentName eventReceiver)
+ {
+ Log.i(TAG, " Remote Control unregisterMediaButtonIntent() for " + mediaIntent);
synchronized(mAudioFocusLock) {
synchronized(mRCStack) {
- boolean topOfStackWillChange = isCurrentRcController(eventReceiver);
- removeMediaButtonReceiver(eventReceiver);
+ boolean topOfStackWillChange = isCurrentRcController(mediaIntent);
+ removeMediaButtonReceiver(mediaIntent);
if (topOfStackWillChange) {
// current RC client will change, assume every type of info needs to be queried
checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL);
@@ -3331,8 +3367,8 @@
}
/** see AudioManager.registerRemoteControlClient(ComponentName eventReceiver, ...) */
- public void registerRemoteControlClient(ComponentName eventReceiver,
- IRemoteControlClient rcClient, String clientName, String callingPackageName) {
+ public void registerRemoteControlClient(PendingIntent mediaIntent,
+ IRemoteControlClient rcClient, String callingPackageName) {
if (DEBUG_RC) Log.i(TAG, "Register remote control client rcClient="+rcClient);
synchronized(mAudioFocusLock) {
synchronized(mRCStack) {
@@ -3340,7 +3376,7 @@
Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
while(stackIterator.hasNext()) {
RemoteControlStackEntry rcse = stackIterator.next();
- if(rcse.mReceiverComponent.equals(eventReceiver)) {
+ if(rcse.mMediaIntent.equals(mediaIntent)) {
// already had a remote control client?
if (rcse.mRcClientDeathHandler != null) {
// stop monitoring the old client's death
@@ -3357,7 +3393,6 @@
}
}
rcse.mCallingPackageName = callingPackageName;
- rcse.mRcClientName = clientName;
rcse.mCallingUid = Binder.getCallingUid();
if (rcClient == null) {
rcse.mRcClientDeathHandler = null;
@@ -3366,7 +3401,7 @@
// monitor the new client's death
IBinder b = rcClient.asBinder();
RcClientDeathHandler rcdh =
- new RcClientDeathHandler(b, rcse.mReceiverComponent);
+ new RcClientDeathHandler(b, rcse.mMediaIntent);
try {
b.linkToDeath(rcdh, 0);
} catch (RemoteException e) {
@@ -3380,7 +3415,7 @@
}
// if the eventReceiver is at the top of the stack
// then check for potential refresh of the remote controls
- if (isCurrentRcController(eventReceiver)) {
+ if (isCurrentRcController(mediaIntent)) {
checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL);
}
}
@@ -3388,24 +3423,23 @@
}
/**
- * see AudioManager.unregisterRemoteControlClient(ComponentName eventReceiver, ...)
+ * see AudioManager.unregisterRemoteControlClient(PendingIntent pi, ...)
* rcClient is guaranteed non-null
*/
- public void unregisterRemoteControlClient(ComponentName eventReceiver,
+ public void unregisterRemoteControlClient(PendingIntent mediaIntent,
IRemoteControlClient rcClient) {
synchronized(mAudioFocusLock) {
synchronized(mRCStack) {
Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
while(stackIterator.hasNext()) {
RemoteControlStackEntry rcse = stackIterator.next();
- if ((rcse.mReceiverComponent.equals(eventReceiver))
+ if ((rcse.mMediaIntent.equals(mediaIntent))
&& rcClient.equals(rcse.mRcClient)) {
// we found the IRemoteControlClient to unregister
// stop monitoring its death
rcse.unlinkToRcClientDeath();
// reset the client-related fields
rcse.mRcClient = null;
- rcse.mRcClientName = null;
rcse.mRcClientDeathHandler = null;
rcse.mCallingPackageName = null;
}
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 7bf9814..5294d36 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -16,6 +16,7 @@
package android.media;
+import android.app.PendingIntent;
import android.content.ComponentName;
import android.media.IAudioFocusDispatcher;
import android.media.IRemoteControlClient;
@@ -85,13 +86,12 @@
void unregisterAudioFocusClient(String clientId);
- void registerMediaButtonEventReceiver(in ComponentName eventReceiver);
+ oneway void registerMediaButtonIntent(in PendingIntent pi, in ComponentName c);
+ oneway void unregisterMediaButtonIntent(in PendingIntent pi, in ComponentName c);
- void unregisterMediaButtonEventReceiver(in ComponentName eventReceiver);
-
- oneway void registerRemoteControlClient(in ComponentName eventReceiver,
- in IRemoteControlClient rcClient, in String clientName, in String callingPackageName);
- oneway void unregisterRemoteControlClient(in ComponentName eventReceiver,
+ oneway void registerRemoteControlClient(in PendingIntent mediaIntent,
+ in IRemoteControlClient rcClient, in String callingPackageName);
+ oneway void unregisterRemoteControlClient(in PendingIntent mediaIntent,
in IRemoteControlClient rcClient);
oneway void registerRemoteControlDisplay(in IRemoteControlDisplay rcd);
diff --git a/media/java/android/media/IRemoteControlDisplay.aidl b/media/java/android/media/IRemoteControlDisplay.aidl
index fd50b7e..e15b07c 100644
--- a/media/java/android/media/IRemoteControlDisplay.aidl
+++ b/media/java/android/media/IRemoteControlDisplay.aidl
@@ -16,6 +16,7 @@
package android.media;
+import android.app.PendingIntent;
import android.content.ComponentName;
import android.graphics.Bitmap;
import android.os.Bundle;
@@ -31,14 +32,12 @@
/**
* Sets the generation counter of the current client that is displayed on the remote control.
* @param clientGeneration the new RemoteControlClient generation
- * @param clientEventReceiver the media button event receiver associated with the client.
- * May be null, which implies there is no registered media button event receiver. This
- * parameter is supplied as an optimization so a display can directly target media button
- * events to the client.
+ * @param clientMediaIntent the PendingIntent associated with the client.
+ * May be null, which implies there is no registered media button event receiver.
* @param clearing true if the new client generation value maps to a remote control update
* where the display should be cleared.
*/
- void setCurrentClientId(int clientGeneration, in ComponentName clientEventReceiver,
+ void setCurrentClientId(int clientGeneration, in PendingIntent clientMediaIntent,
boolean clearing);
void setPlaybackState(int generationId, int state);
diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java
index cdebba0..5dea87f 100644
--- a/media/java/android/media/RemoteControlClient.java
+++ b/media/java/android/media/RemoteControlClient.java
@@ -34,6 +34,7 @@
import java.lang.IllegalArgumentException;
/**
+ * TODO javadoc update for ComponentName - PendingIntent change
* RemoteControlClient enables exposing information meant to be consumed by remote controls
* capable of displaying metadata, artwork and media transport control buttons.
* A remote control client object is associated with a media button event receiver. This
@@ -205,50 +206,6 @@
public final static int FLAG_INFORMATION_REQUEST_ALBUM_ART = 1 << 3;
/**
- * @hide
- * TODO remove after modifying known (internal) media apps using this API
- * Class constructor.
- * @param mediaButtonEventReceiver The receiver for the media button events. It needs to have
- * been registered with {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)}
- * before this new RemoteControlClient can itself be registered with
- * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.
- * @see AudioManager#registerMediaButtonEventReceiver(ComponentName)
- * @see AudioManager#registerRemoteControlClient(RemoteControlClient)
- */
- public RemoteControlClient(ComponentName mediaButtonEventReceiver) {
- mRcEventReceiver = mediaButtonEventReceiver;
-
- Looper looper;
- if ((looper = Looper.myLooper()) != null) {
- mEventHandler = new EventHandler(this, looper);
- } else if ((looper = Looper.getMainLooper()) != null) {
- mEventHandler = new EventHandler(this, looper);
- } else {
- mEventHandler = null;
- Log.e(TAG, "RemoteControlClient() couldn't find main application thread");
- }
- }
-
- /**
- * @hide
- * TODO remove after modifying known (internal) media apps using this API
- * Class constructor for a remote control client whose internal event handling
- * happens on a user-provided Looper.
- * @param mediaButtonEventReceiver The receiver for the media button events. It needs to have
- * been registered with {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)}
- * before this new RemoteControlClient can itself be registered with
- * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.
- * @param looper The Looper running the event loop.
- * @see AudioManager#registerMediaButtonEventReceiver(ComponentName)
- * @see AudioManager#registerRemoteControlClient(RemoteControlClient)
- */
- public RemoteControlClient(ComponentName mediaButtonEventReceiver, Looper looper) {
- mRcEventReceiver = mediaButtonEventReceiver;
-
- mEventHandler = new EventHandler(this, looper);
- }
-
- /**
* Class constructor.
* @param mediaButtonIntent The intent that will be sent for the media button events sent
* by remote controls.
@@ -262,8 +219,7 @@
* @see AudioManager#registerRemoteControlClient(RemoteControlClient)
*/
public RemoteControlClient(PendingIntent mediaButtonIntent) {
- // TODO implement using PendingIntent instead of ComponentName
- mRcEventReceiver = null;
+ mRcMediaIntent = mediaButtonIntent;
Looper looper;
if ((looper = Looper.myLooper()) != null) {
@@ -292,8 +248,7 @@
* @see AudioManager#registerRemoteControlClient(RemoteControlClient)
*/
public RemoteControlClient(PendingIntent mediaButtonIntent, Looper looper) {
- // TODO implement using PendingIntent instead of ComponentName
- mRcEventReceiver = null;
+ mRcMediaIntent = mediaButtonIntent;
mEventHandler = new EventHandler(this, looper);
}
@@ -614,9 +569,10 @@
private int mInternalClientGenId = -2;
/**
- * The media button event receiver associated with this remote control client
+ * The media button intent description associated with this remote control client
+ * (can / should include target component for intent handling)
*/
- private final ComponentName mRcEventReceiver;
+ private final PendingIntent mRcMediaIntent;
/**
* The remote control display to which this client will send information.
@@ -626,10 +582,10 @@
/**
* @hide
- * Accessor to media button event receiver
+ * Accessor to media button intent description (includes target component)
*/
- public ComponentName getRcEventReceiver() {
- return mRcEventReceiver;
+ public PendingIntent getRcMediaIntent() {
+ return mRcMediaIntent;
}
/**
* @hide
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
index 6f34ba7..7218faf 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
@@ -34,17 +34,21 @@
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/ACodec.h>
+#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/MetaData.h>
#include <surfaceflinger/Surface.h>
#include <gui/ISurfaceTexture.h>
+#include "avc_utils.h"
+
namespace android {
////////////////////////////////////////////////////////////////////////////////
NuPlayer::NuPlayer()
: mUIDValid(false),
+ mVideoIsAVC(false),
mAudioEOS(false),
mVideoEOS(false),
mScanSourcesPending(false),
@@ -52,7 +56,12 @@
mFlushingAudio(NONE),
mFlushingVideo(NONE),
mResetInProgress(false),
- mResetPostponed(false) {
+ mResetPostponed(false),
+ mSkipRenderingAudioUntilMediaTimeUs(-1ll),
+ mSkipRenderingVideoUntilMediaTimeUs(-1ll),
+ mVideoLateByUs(0ll),
+ mNumFramesTotal(0ll),
+ mNumFramesDropped(0ll) {
}
NuPlayer::~NuPlayer() {
@@ -185,10 +194,14 @@
{
LOGV("kWhatStart");
+ mVideoIsAVC = false;
mAudioEOS = false;
mVideoEOS = false;
mSkipRenderingAudioUntilMediaTimeUs = -1;
mSkipRenderingVideoUntilMediaTimeUs = -1;
+ mVideoLateByUs = 0;
+ mNumFramesTotal = 0;
+ mNumFramesDropped = 0;
mSource->start();
@@ -269,6 +282,8 @@
} else {
CHECK(IsFlushingState(mFlushingVideo, &needShutdown));
mFlushingVideo = FLUSHED;
+
+ mVideoLateByUs = 0;
}
LOGV("decoder %s flush completed", audio ? "audio" : "video");
@@ -397,13 +412,18 @@
int64_t positionUs;
CHECK(msg->findInt64("positionUs", &positionUs));
+ CHECK(msg->findInt64("videoLateByUs", &mVideoLateByUs));
+
if (mDriver != NULL) {
sp<NuPlayerDriver> driver = mDriver.promote();
if (driver != NULL) {
driver->notifyPosition(positionUs);
+
+ driver->notifyFrameStats(
+ mNumFramesTotal, mNumFramesDropped);
}
}
- } else {
+ } else if (what == Renderer::kWhatFlushComplete) {
CHECK_EQ(what, (int32_t)Renderer::kWhatFlushComplete);
int32_t audio;
@@ -565,6 +585,12 @@
return -EWOULDBLOCK;
}
+ if (!audio) {
+ const char *mime;
+ CHECK(meta->findCString(kKeyMIMEType, &mime));
+ mVideoIsAVC = !strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime);
+ }
+
sp<AMessage> notify =
new AMessage(audio ? kWhatAudioNotify : kWhatVideoNotify,
id());
@@ -598,53 +624,70 @@
}
sp<ABuffer> accessUnit;
- status_t err = mSource->dequeueAccessUnit(audio, &accessUnit);
- if (err == -EWOULDBLOCK) {
- return err;
- } else if (err != OK) {
- if (err == INFO_DISCONTINUITY) {
- int32_t type;
- CHECK(accessUnit->meta()->findInt32("discontinuity", &type));
+ bool dropAccessUnit;
+ do {
+ status_t err = mSource->dequeueAccessUnit(audio, &accessUnit);
- bool formatChange =
- type == ATSParser::DISCONTINUITY_FORMATCHANGE;
+ if (err == -EWOULDBLOCK) {
+ return err;
+ } else if (err != OK) {
+ if (err == INFO_DISCONTINUITY) {
+ int32_t type;
+ CHECK(accessUnit->meta()->findInt32("discontinuity", &type));
- LOGV("%s discontinuity (formatChange=%d)",
- audio ? "audio" : "video", formatChange);
+ bool formatChange =
+ type == ATSParser::DISCONTINUITY_FORMATCHANGE;
- if (audio) {
- mSkipRenderingAudioUntilMediaTimeUs = -1;
- } else {
- mSkipRenderingVideoUntilMediaTimeUs = -1;
- }
+ LOGV("%s discontinuity (formatChange=%d)",
+ audio ? "audio" : "video", formatChange);
- sp<AMessage> extra;
- if (accessUnit->meta()->findMessage("extra", &extra)
- && extra != NULL) {
- int64_t resumeAtMediaTimeUs;
- if (extra->findInt64(
- "resume-at-mediatimeUs", &resumeAtMediaTimeUs)) {
- LOGI("suppressing rendering of %s until %lld us",
- audio ? "audio" : "video", resumeAtMediaTimeUs);
+ if (audio) {
+ mSkipRenderingAudioUntilMediaTimeUs = -1;
+ } else {
+ mSkipRenderingVideoUntilMediaTimeUs = -1;
+ }
- if (audio) {
- mSkipRenderingAudioUntilMediaTimeUs =
- resumeAtMediaTimeUs;
- } else {
- mSkipRenderingVideoUntilMediaTimeUs =
- resumeAtMediaTimeUs;
+ sp<AMessage> extra;
+ if (accessUnit->meta()->findMessage("extra", &extra)
+ && extra != NULL) {
+ int64_t resumeAtMediaTimeUs;
+ if (extra->findInt64(
+ "resume-at-mediatimeUs", &resumeAtMediaTimeUs)) {
+ LOGI("suppressing rendering of %s until %lld us",
+ audio ? "audio" : "video", resumeAtMediaTimeUs);
+
+ if (audio) {
+ mSkipRenderingAudioUntilMediaTimeUs =
+ resumeAtMediaTimeUs;
+ } else {
+ mSkipRenderingVideoUntilMediaTimeUs =
+ resumeAtMediaTimeUs;
+ }
}
}
+
+ flushDecoder(audio, formatChange);
}
- flushDecoder(audio, formatChange);
+ reply->setInt32("err", err);
+ reply->post();
+ return OK;
}
- reply->setInt32("err", err);
- reply->post();
- return OK;
- }
+ if (!audio) {
+ ++mNumFramesTotal;
+ }
+
+ dropAccessUnit = false;
+ if (!audio
+ && mVideoLateByUs > 100000ll
+ && mVideoIsAVC
+ && !IsAVCReferenceFrame(accessUnit)) {
+ dropAccessUnit = true;
+ ++mNumFramesDropped;
+ }
+ } while (dropAccessUnit);
// LOGV("returned a valid buffer of %s data", audio ? "audio" : "video");
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.h b/media/libmediaplayerservice/nuplayer/NuPlayer.h
index 41e015b..a5382b4 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.h
@@ -92,6 +92,7 @@
sp<NativeWindowWrapper> mNativeWindow;
sp<MediaPlayerBase::AudioSink> mAudioSink;
sp<Decoder> mVideoDecoder;
+ bool mVideoIsAVC;
sp<Decoder> mAudioDecoder;
sp<Renderer> mRenderer;
@@ -119,6 +120,9 @@
int64_t mSkipRenderingAudioUntilMediaTimeUs;
int64_t mSkipRenderingVideoUntilMediaTimeUs;
+ int64_t mVideoLateByUs;
+ int64_t mNumFramesTotal, mNumFramesDropped;
+
status_t instantiateDecoder(bool audio, sp<Decoder> *decoder);
status_t feedDecoderInputData(bool audio, const sp<AMessage> &msg);
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
index c6fca2c..b1e917d 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
@@ -31,6 +31,8 @@
: mResetInProgress(false),
mDurationUs(-1),
mPositionUs(-1),
+ mNumFramesTotal(0),
+ mNumFramesDropped(0),
mLooper(new ALooper),
mState(UNINITIALIZED),
mStartupSeekTimeUs(-1) {
@@ -292,4 +294,30 @@
sendEvent(MEDIA_SEEK_COMPLETE);
}
+void NuPlayerDriver::notifyFrameStats(
+ int64_t numFramesTotal, int64_t numFramesDropped) {
+ Mutex::Autolock autoLock(mLock);
+ mNumFramesTotal = numFramesTotal;
+ mNumFramesDropped = numFramesDropped;
+}
+
+status_t NuPlayerDriver::dump(int fd, const Vector<String16> &args) const {
+ Mutex::Autolock autoLock(mLock);
+
+ FILE *out = fdopen(dup(fd), "w");
+
+ fprintf(out, " NuPlayer\n");
+ fprintf(out, " numFramesTotal(%lld), numFramesDropped(%lld), "
+ "percentageDropped(%.2f)\n",
+ mNumFramesTotal,
+ mNumFramesDropped,
+ mNumFramesTotal == 0
+ ? 0.0 : (double)mNumFramesDropped / mNumFramesTotal);
+
+ fclose(out);
+ out = NULL;
+
+ return OK;
+}
+
} // namespace android
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
index 1bb7ca2..181c37d 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
@@ -60,16 +60,19 @@
virtual status_t getMetadata(
const media::Metadata::Filter& ids, Parcel *records);
+ virtual status_t dump(int fd, const Vector<String16> &args) const;
+
void notifyResetComplete();
void notifyDuration(int64_t durationUs);
void notifyPosition(int64_t positionUs);
void notifySeekComplete();
+ void notifyFrameStats(int64_t numFramesTotal, int64_t numFramesDropped);
protected:
virtual ~NuPlayerDriver();
private:
- Mutex mLock;
+ mutable Mutex mLock;
Condition mCondition;
// The following are protected through "mLock"
@@ -77,6 +80,8 @@
bool mResetInProgress;
int64_t mDurationUs;
int64_t mPositionUs;
+ int64_t mNumFramesTotal;
+ int64_t mNumFramesDropped;
// <<<
sp<ALooper> mLooper;
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
index 3b3fca2..07e347e 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
@@ -47,7 +47,8 @@
mHasVideo(false),
mSyncQueues(false),
mPaused(false),
- mLastPositionUpdateUs(-1ll) {
+ mLastPositionUpdateUs(-1ll),
+ mVideoLateByUs(0ll) {
}
NuPlayer::Renderer::~Renderer() {
@@ -357,22 +358,26 @@
mVideoQueue.erase(mVideoQueue.begin());
entry = NULL;
+
+ mVideoLateByUs = 0ll;
+
+ notifyPosition();
return;
}
-#if 0
int64_t mediaTimeUs;
CHECK(entry->mBuffer->meta()->findInt64("timeUs", &mediaTimeUs));
int64_t realTimeUs = mediaTimeUs - mAnchorTimeMediaUs + mAnchorTimeRealUs;
- int64_t lateByUs = ALooper::GetNowUs() - realTimeUs;
+ mVideoLateByUs = ALooper::GetNowUs() - realTimeUs;
- if (lateByUs > 40000) {
- LOGI("video late by %lld us (%.2f secs)", lateByUs, lateByUs / 1E6);
+ bool tooLate = (mVideoLateByUs > 40000);
+
+ if (tooLate) {
+ LOGV("video late by %lld us (%.2f secs)", lateByUs, lateByUs / 1E6);
} else {
LOGV("rendering video at media time %.2f secs", mediaTimeUs / 1E6);
}
-#endif
entry->mNotifyConsumed->setInt32("render", true);
entry->mNotifyConsumed->post();
@@ -604,6 +609,7 @@
sp<AMessage> notify = mNotify->dup();
notify->setInt32("what", kWhatPosition);
notify->setInt64("positionUs", positionUs);
+ notify->setInt64("videoLateByUs", mVideoLateByUs);
notify->post();
}
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h
index 44c5d44..268628b 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h
@@ -101,6 +101,7 @@
bool mPaused;
int64_t mLastPositionUpdateUs;
+ int64_t mVideoLateByUs;
bool onDrainAudioQueue();
void postDrainAudioQueue(int64_t delayUs = 0);
@@ -118,6 +119,7 @@
void notifyEOS(bool audio, status_t finalResult);
void notifyFlushComplete(bool audio);
void notifyPosition();
+ void notifyVideoLateBy(int64_t lateByUs);
void flushQueue(List<QueueEntry> *queue);
bool dropBufferWhileFlushing(bool audio, const sp<AMessage> &msg);
diff --git a/media/libstagefright/SampleTable.cpp b/media/libstagefright/SampleTable.cpp
index 2b9d99b..ebad321 100644
--- a/media/libstagefright/SampleTable.cpp
+++ b/media/libstagefright/SampleTable.cpp
@@ -23,8 +23,8 @@
#include <arpa/inet.h>
+#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/DataSource.h>
-#include <media/stagefright/MediaDebug.h>
#include <media/stagefright/Utils.h>
namespace android {
@@ -40,6 +40,71 @@
////////////////////////////////////////////////////////////////////////////////
+struct SampleTable::CompositionDeltaLookup {
+ CompositionDeltaLookup();
+
+ void setEntries(
+ const uint32_t *deltaEntries, size_t numDeltaEntries);
+
+ uint32_t getCompositionTimeOffset(uint32_t sampleIndex);
+
+private:
+ Mutex mLock;
+
+ const uint32_t *mDeltaEntries;
+ size_t mNumDeltaEntries;
+
+ size_t mCurrentDeltaEntry;
+ size_t mCurrentEntrySampleIndex;
+
+ DISALLOW_EVIL_CONSTRUCTORS(CompositionDeltaLookup);
+};
+
+SampleTable::CompositionDeltaLookup::CompositionDeltaLookup()
+ : mDeltaEntries(NULL),
+ mNumDeltaEntries(0),
+ mCurrentDeltaEntry(0),
+ mCurrentEntrySampleIndex(0) {
+}
+
+void SampleTable::CompositionDeltaLookup::setEntries(
+ const uint32_t *deltaEntries, size_t numDeltaEntries) {
+ Mutex::Autolock autolock(mLock);
+
+ mDeltaEntries = deltaEntries;
+ mNumDeltaEntries = numDeltaEntries;
+ mCurrentDeltaEntry = 0;
+ mCurrentEntrySampleIndex = 0;
+}
+
+uint32_t SampleTable::CompositionDeltaLookup::getCompositionTimeOffset(
+ uint32_t sampleIndex) {
+ Mutex::Autolock autolock(mLock);
+
+ if (mDeltaEntries == NULL) {
+ return 0;
+ }
+
+ if (sampleIndex < mCurrentEntrySampleIndex) {
+ mCurrentDeltaEntry = 0;
+ mCurrentEntrySampleIndex = 0;
+ }
+
+ while (mCurrentDeltaEntry < mNumDeltaEntries) {
+ uint32_t sampleCount = mDeltaEntries[2 * mCurrentDeltaEntry];
+ if (sampleIndex < mCurrentEntrySampleIndex + sampleCount) {
+ return mDeltaEntries[2 * mCurrentDeltaEntry + 1];
+ }
+
+ mCurrentEntrySampleIndex += sampleCount;
+ ++mCurrentDeltaEntry;
+ }
+
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
SampleTable::SampleTable(const sp<DataSource> &source)
: mDataSource(source),
mChunkOffsetOffset(-1),
@@ -56,6 +121,7 @@
mSampleTimeEntries(NULL),
mCompositionTimeDeltaEntries(NULL),
mNumCompositionTimeDeltaEntries(0),
+ mCompositionDeltaLookup(new CompositionDeltaLookup),
mSyncSampleOffset(-1),
mNumSyncSamples(0),
mSyncSamples(NULL),
@@ -71,6 +137,9 @@
delete[] mSyncSamples;
mSyncSamples = NULL;
+ delete mCompositionDeltaLookup;
+ mCompositionDeltaLookup = NULL;
+
delete[] mCompositionTimeDeltaEntries;
mCompositionTimeDeltaEntries = NULL;
@@ -318,6 +387,9 @@
mCompositionTimeDeltaEntries[i] = ntohl(mCompositionTimeDeltaEntries[i]);
}
+ mCompositionDeltaLookup->setEntries(
+ mCompositionTimeDeltaEntries, mNumCompositionTimeDeltaEntries);
+
return OK;
}
@@ -430,8 +502,12 @@
mSampleTimeEntries[sampleIndex].mSampleIndex = sampleIndex;
+ uint32_t compTimeDelta =
+ mCompositionDeltaLookup->getCompositionTimeOffset(
+ sampleIndex);
+
mSampleTimeEntries[sampleIndex].mCompositionTime =
- sampleTime + getCompositionTimeOffset(sampleIndex);
+ sampleTime + compTimeDelta;
}
++sampleIndex;
@@ -739,25 +815,8 @@
return OK;
}
-uint32_t SampleTable::getCompositionTimeOffset(uint32_t sampleIndex) const {
- if (mCompositionTimeDeltaEntries == NULL) {
- return 0;
- }
-
- uint32_t curSample = 0;
- for (size_t i = 0; i < mNumCompositionTimeDeltaEntries; ++i) {
- uint32_t sampleCount = mCompositionTimeDeltaEntries[2 * i];
-
- if (sampleIndex < curSample + sampleCount) {
- uint32_t sampleDelta = mCompositionTimeDeltaEntries[2 * i + 1];
-
- return sampleDelta;
- }
-
- curSample += sampleCount;
- }
-
- return 0;
+uint32_t SampleTable::getCompositionTimeOffset(uint32_t sampleIndex) {
+ return mCompositionDeltaLookup->getCompositionTimeOffset(sampleIndex);
}
} // namespace android
diff --git a/media/libstagefright/avc_utils.cpp b/media/libstagefright/avc_utils.cpp
index 8a42e8b..07aa140 100644
--- a/media/libstagefright/avc_utils.cpp
+++ b/media/libstagefright/avc_utils.cpp
@@ -329,6 +329,28 @@
return foundIDR;
}
+bool IsAVCReferenceFrame(const sp<ABuffer> &accessUnit) {
+ const uint8_t *data = accessUnit->data();
+ size_t size = accessUnit->size();
+
+ const uint8_t *nalStart;
+ size_t nalSize;
+ while (getNextNALUnit(&data, &size, &nalStart, &nalSize, true) == OK) {
+ CHECK_GT(nalSize, 0u);
+
+ unsigned nalType = nalStart[0] & 0x1f;
+
+ if (nalType == 5) {
+ return true;
+ } else if (nalType == 1) {
+ unsigned nal_ref_idc = (nalStart[0] >> 5) & 3;
+ return nal_ref_idc != 0;
+ }
+ }
+
+ return true;
+}
+
sp<MetaData> MakeAACCodecSpecificData(
unsigned profile, unsigned sampling_freq_index,
unsigned channel_configuration) {
diff --git a/media/libstagefright/include/SampleTable.h b/media/libstagefright/include/SampleTable.h
index a6a6524..847dff7 100644
--- a/media/libstagefright/include/SampleTable.h
+++ b/media/libstagefright/include/SampleTable.h
@@ -86,6 +86,8 @@
~SampleTable();
private:
+ struct CompositionDeltaLookup;
+
static const uint32_t kChunkOffsetType32;
static const uint32_t kChunkOffsetType64;
static const uint32_t kSampleSizeType32;
@@ -117,6 +119,7 @@
uint32_t *mCompositionTimeDeltaEntries;
size_t mNumCompositionTimeDeltaEntries;
+ CompositionDeltaLookup *mCompositionDeltaLookup;
off64_t mSyncSampleOffset;
uint32_t mNumSyncSamples;
@@ -135,8 +138,7 @@
friend struct SampleIterator;
status_t getSampleSize_l(uint32_t sample_index, size_t *sample_size);
-
- uint32_t getCompositionTimeOffset(uint32_t sampleIndex) const;
+ uint32_t getCompositionTimeOffset(uint32_t sampleIndex);
static int CompareIncreasingTime(const void *, const void *);
diff --git a/media/libstagefright/include/avc_utils.h b/media/libstagefright/include/avc_utils.h
index 15cd4d4..e418822 100644
--- a/media/libstagefright/include/avc_utils.h
+++ b/media/libstagefright/include/avc_utils.h
@@ -50,6 +50,7 @@
sp<MetaData> MakeAVCCodecSpecificData(const sp<ABuffer> &accessUnit);
bool IsIDR(const sp<ABuffer> &accessUnit);
+bool IsAVCReferenceFrame(const sp<ABuffer> &accessUnit);
const char *AVCProfileToString(uint8_t profile);
diff --git a/opengl/libs/GLES2_dbg/Android.mk b/opengl/libs/GLES2_dbg/Android.mk
index c2b1142..70853d8 100644
--- a/opengl/libs/GLES2_dbg/Android.mk
+++ b/opengl/libs/GLES2_dbg/Android.mk
@@ -31,6 +31,9 @@
LOCAL_CFLAGS += -DHAVE_ARM_TLS_REGISTER
endif
+LOCAL_CFLAGS += -DLOG_TAG=\"libGLES2_dbg\"
+
+
# we need to access the private Bionic header <bionic_tls.h>
# on ARM platforms, we need to mirror the ARCH_ARM_HAVE_TLS_REGISTER
# behavior from the bionic Android.mk file
diff --git a/opengl/libs/GLES2_dbg/src/dbgcontext.cpp b/opengl/libs/GLES2_dbg/src/dbgcontext.cpp
index 9e77665..41061e1 100644
--- a/opengl/libs/GLES2_dbg/src/dbgcontext.cpp
+++ b/opengl/libs/GLES2_dbg/src/dbgcontext.cpp
@@ -30,13 +30,11 @@
}
DbgContext::DbgContext(const unsigned version, const gl_hooks_t * const hooks,
- const unsigned MAX_VERTEX_ATTRIBS, const GLenum readFormat,
- const GLenum readType)
+ const unsigned MAX_VERTEX_ATTRIBS)
: lzf_buf(NULL), lzf_readIndex(0), lzf_refSize(0), lzf_refBufSize(0)
, version(version), hooks(hooks)
, MAX_VERTEX_ATTRIBS(MAX_VERTEX_ATTRIBS)
- , readFormat(readFormat), readType(readType)
- , readBytesPerPixel(GetBytesPerPixel(readFormat, readType))
+ , readBytesPerPixel(4)
, captureSwap(0), captureDraw(0)
, vertexAttribs(new VertexAttrib[MAX_VERTEX_ATTRIBS])
, hasNonVBOAttribs(false), indexBuffers(NULL), indexBuffer(NULL)
@@ -67,11 +65,7 @@
assert(GL_NO_ERROR == hooks->gl.glGetError());
GLint MAX_VERTEX_ATTRIBS = 0;
hooks->gl.glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &MAX_VERTEX_ATTRIBS);
- GLint readFormat, readType;
- hooks->gl.glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &readFormat);
- hooks->gl.glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &readType);
- DbgContext* dbg = new DbgContext(version, hooks, MAX_VERTEX_ATTRIBS, readFormat, readType);
-
+ DbgContext* dbg = new DbgContext(version, hooks, MAX_VERTEX_ATTRIBS);
glesv2debugger::Message msg, cmd;
msg.set_context_id(reinterpret_cast<int>(dbg));
msg.set_expect_response(false);
@@ -100,33 +94,31 @@
{
switch (type) {
case GL_UNSIGNED_SHORT_5_6_5:
- return 2;
case GL_UNSIGNED_SHORT_4_4_4_4:
- return 2;
case GL_UNSIGNED_SHORT_5_5_5_1:
return 2;
case GL_UNSIGNED_BYTE:
break;
default:
- assert(0);
+ LOGE("GetBytesPerPixel: unknown type %x", type);
}
switch (format) {
case GL_ALPHA:
- return 1;
case GL_LUMINANCE:
return 1;
- break;
case GL_LUMINANCE_ALPHA:
return 2;
case GL_RGB:
return 3;
case GL_RGBA:
+ case 0x80E1: // GL_BGRA_EXT
return 4;
default:
- assert(0);
- return 0;
+ LOGE("GetBytesPerPixel: unknown format %x", format);
}
+
+ return 1; // in doubt...
}
void DbgContext::Fetch(const unsigned index, std::string * const data) const
diff --git a/opengl/libs/GLES2_dbg/src/egl.cpp b/opengl/libs/GLES2_dbg/src/egl.cpp
index eb28d06..bbea3bd 100644
--- a/opengl/libs/GLES2_dbg/src/egl.cpp
+++ b/opengl/libs/GLES2_dbg/src/egl.cpp
@@ -41,11 +41,11 @@
void * pixels = dbg->GetReadPixelsBuffer(viewport[2] * viewport[3] *
dbg->readBytesPerPixel);
dbg->hooks->gl.glReadPixels(viewport[0], viewport[1], viewport[2],
- viewport[3], dbg->readFormat, dbg->readType, pixels);
+ viewport[3], GL_RGBA, GL_UNSIGNED_BYTE, pixels);
dbg->CompressReadPixelBuffer(msg.mutable_data());
msg.set_data_type(msg.ReferencedImage);
- msg.set_pixel_format(dbg->readFormat);
- msg.set_pixel_type(dbg->readType);
+ msg.set_pixel_format(GL_RGBA);
+ msg.set_pixel_type(GL_UNSIGNED_BYTE);
msg.set_image_width(viewport[2]);
msg.set_image_height(viewport[3]);
}
diff --git a/opengl/libs/GLES2_dbg/src/header.h b/opengl/libs/GLES2_dbg/src/header.h
index f2b1fa6..49f3847 100644
--- a/opengl/libs/GLES2_dbg/src/header.h
+++ b/opengl/libs/GLES2_dbg/src/header.h
@@ -87,7 +87,6 @@
const unsigned int version; // 0 is GLES1, 1 is GLES2
const gl_hooks_t * const hooks;
const unsigned int MAX_VERTEX_ATTRIBS;
- const GLenum readFormat, readType; // implementation supported glReadPixels
const unsigned int readBytesPerPixel;
unsigned int captureSwap; // number of eglSwapBuffers to glReadPixels
@@ -124,8 +123,7 @@
unsigned maxAttrib; // number of slots used by program
DbgContext(const unsigned version, const gl_hooks_t * const hooks,
- const unsigned MAX_VERTEX_ATTRIBS, const GLenum readFormat,
- const GLenum readType);
+ const unsigned MAX_VERTEX_ATTRIBS);
~DbgContext();
void Fetch(const unsigned index, std::string * const data) const;
diff --git a/opengl/libs/GLES2_dbg/src/vertex.cpp b/opengl/libs/GLES2_dbg/src/vertex.cpp
index 029ee3b..28a24206 100644
--- a/opengl/libs/GLES2_dbg/src/vertex.cpp
+++ b/opengl/libs/GLES2_dbg/src/vertex.cpp
@@ -77,7 +77,7 @@
pixels = dbg->GetReadPixelsBuffer(viewport[2] * viewport[3] *
dbg->readBytesPerPixel);
Debug_glReadPixels(viewport[0], viewport[1], viewport[2], viewport[3],
- dbg->readFormat, dbg->readType, pixels);
+ GL_RGBA, GL_UNSIGNED_BYTE, pixels);
}
break;
case glesv2debugger::Message_Function_SKIP:
@@ -134,19 +134,22 @@
msg.set_arg7(dbg->maxAttrib); // indicate capturing vertex data
std::string * const data = msg.mutable_data();
if (GL_UNSIGNED_BYTE == type) {
- if (dbg->indexBuffer)
+ if (dbg->indexBuffer) {
FetchIndexed(count, (unsigned char *)dbg->indexBuffer->data +
(unsigned long)indices, data, dbg);
- else
+ } else {
FetchIndexed(count, (unsigned char *)indices, data, dbg);
+ }
} else if (GL_UNSIGNED_SHORT == type) {
- if (dbg->indexBuffer)
+ if (dbg->indexBuffer) {
FetchIndexed(count, (unsigned short *)((char *)dbg->indexBuffer->data +
(unsigned long)indices), data, dbg);
- else
+ } else {
FetchIndexed(count, (unsigned short *)indices, data, dbg);
- } else
+ }
+ } else {
assert(0);
+ }
void * pixels = NULL;
int viewport[4] = {};
@@ -174,7 +177,7 @@
Send(msg, cmd);
expectResponse = cmd.expect_response();
// TODO: pack glReadPixels data with vertex data instead of
- // relying on sperate call for transport, this would allow
+ // relying on separate call for transport, this would allow
// auto generated message loop using EXTEND_Debug macro
if (dbg->captureDraw > 0) {
dbg->captureDraw--;
@@ -182,7 +185,7 @@
pixels = dbg->GetReadPixelsBuffer(viewport[2] * viewport[3] *
dbg->readBytesPerPixel);
Debug_glReadPixels(viewport[0], viewport[1], viewport[2], viewport[3],
- dbg->readFormat, dbg->readType, pixels);
+ GL_RGBA, GL_UNSIGNED_BYTE, pixels);
}
break;
case glesv2debugger::Message_Function_SKIP:
diff --git a/opengl/libs/GLES2_dbg/test/test_main.cpp b/opengl/libs/GLES2_dbg/test/test_main.cpp
index 058bea4..183bf8e 100644
--- a/opengl/libs/GLES2_dbg/test/test_main.cpp
+++ b/opengl/libs/GLES2_dbg/test/test_main.cpp
@@ -29,7 +29,7 @@
gl_hooks_t hooks;
DbgContextTest()
- : dbg(1, &hooks, 32, GL_RGBA, GL_UNSIGNED_BYTE) {
+ : dbg(1, &hooks, 32) {
// You can do set-up work for each test here.
hooks.gl.glGetError = GetError;
}
diff --git a/opengl/libs/GLES2_dbg/test/test_server.cpp b/opengl/libs/GLES2_dbg/test/test_server.cpp
index bbbe913..0ab87b0 100644
--- a/opengl/libs/GLES2_dbg/test/test_server.cpp
+++ b/opengl/libs/GLES2_dbg/test/test_server.cpp
@@ -151,7 +151,7 @@
virtual void SetUp() {
ServerFileTest::SetUp();
- dbg = new DbgContext(1, &hooks, 32, GL_RGBA, GL_UNSIGNED_BYTE);
+ dbg = new DbgContext(1, &hooks, 32);
ASSERT_NE((void *)NULL, dbg);
for (unsigned int i = 0; i < sizeof(hooks) / sizeof(void *); i++)
((void **)&hooks)[i] = reinterpret_cast<void *>(glNoop);
diff --git a/opengl/libs/GLES2_dbg/test/test_socket.cpp b/opengl/libs/GLES2_dbg/test/test_socket.cpp
index b2148ac..9f815e2 100644
--- a/opengl/libs/GLES2_dbg/test/test_socket.cpp
+++ b/opengl/libs/GLES2_dbg/test/test_socket.cpp
@@ -44,7 +44,7 @@
}
virtual void SetUp() {
- dbg = new DbgContext(1, &hooks, 32, GL_RGBA, GL_UNSIGNED_BYTE);
+ dbg = new DbgContext(1, &hooks, 32);
ASSERT_TRUE(dbg != NULL);
for (unsigned int i = 0; i < sizeof(hooks) / sizeof(void *); i++)
((void **)&hooks)[i] = (void *)glNoop;
diff --git a/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml b/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml
index f84cc19..2fe22ff 100644
--- a/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml
+++ b/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml
@@ -69,6 +69,12 @@
</FrameLayout>
+ <include layout="@layout/status_bar_no_recent_apps"
+ android:id="@+id/recents_no_apps"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="invisible" />
+
<View android:id="@+id/recents_dismiss_button"
android:layout_width="80px"
android:layout_height="@*android:dimen/status_bar_height"
diff --git a/packages/SystemUI/res/layout-port/status_bar_recent_panel.xml b/packages/SystemUI/res/layout-port/status_bar_recent_panel.xml
index ed9ea7a..4d49077 100644
--- a/packages/SystemUI/res/layout-port/status_bar_recent_panel.xml
+++ b/packages/SystemUI/res/layout-port/status_bar_recent_panel.xml
@@ -67,6 +67,12 @@
</FrameLayout>
+ <include layout="@layout/status_bar_no_recent_apps"
+ android:id="@+id/recents_no_apps"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="invisible" />
+
<View android:id="@+id/recents_dismiss_button"
android:layout_width="80px"
android:layout_height="@*android:dimen/status_bar_height"
diff --git a/packages/SystemUI/res/layout/status_bar_no_recent_apps.xml b/packages/SystemUI/res/layout/status_bar_no_recent_apps.xml
new file mode 100644
index 0000000..47ffb83
--- /dev/null
+++ b/packages/SystemUI/res/layout/status_bar_no_recent_apps.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* apps/common/assets/default/default/skins/StatusBar.xml
+**
+** Copyright 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.
+*/
+-->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ >
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="24dp"
+ android:textColor="#ffffffff"
+ android:text="@string/status_bar_no_recent_apps"
+ android:gravity="center_horizontal"
+ android:layout_gravity="center"
+ />
+</FrameLayout>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index bad7e1f..b9e6d78 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -41,6 +41,10 @@
<!-- Title shown in recents popup for inspecting an application's properties -->
<string name="status_bar_recent_inspect_item_title">App info</string>
+ <!-- Message shown in the middle of the screen after clicking on the recent apps button
+ when there are no recent apps to show [CHAR LIMIT=45]-->
+ <string name="status_bar_no_recent_apps">No recent apps</string>
+
<!-- The label in the bar at the top of the status bar when there are no notifications
showing. [CHAR LIMIT=40]-->
<string name="status_bar_no_notifications_title">No notifications</string>
diff --git a/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java b/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java
index 9749a1d..fbf00d2 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java
@@ -38,17 +38,20 @@
View mRootView;
View mScrimView;
View mContentView;
+ View mNoRecentAppsView;
AnimatorSet mContentAnim;
Animator.AnimatorListener mListener;
// the panel will start to appear this many px from the end
final int HYPERSPACE_OFFRAMP = 200;
- public Choreographer(View root, View scrim, View content, Animator.AnimatorListener listener) {
+ public Choreographer(View root, View scrim, View content,
+ View noRecentApps, Animator.AnimatorListener listener) {
mRootView = root;
mScrimView = scrim;
mContentView = content;
mListener = listener;
+ mNoRecentAppsView = noRecentApps;
}
void createAnimation(boolean appearing) {
@@ -81,8 +84,24 @@
: new android.view.animation.DecelerateInterpolator(1.0f));
glowAnim.setDuration(appearing ? OPEN_DURATION : CLOSE_DURATION);
+ Animator noRecentAppsFadeAnim = null;
+ if (mNoRecentAppsView != null && // doesn't exist on large devices
+ mNoRecentAppsView.getVisibility() == View.VISIBLE) {
+ noRecentAppsFadeAnim = ObjectAnimator.ofFloat(mNoRecentAppsView, "alpha",
+ mContentView.getAlpha(), appearing ? 1.0f : 0.0f);
+ noRecentAppsFadeAnim.setInterpolator(appearing
+ ? new android.view.animation.AccelerateInterpolator(1.0f)
+ : new android.view.animation.DecelerateInterpolator(1.0f));
+ noRecentAppsFadeAnim.setDuration(appearing ? OPEN_DURATION : CLOSE_DURATION);
+ }
+
mContentAnim = new AnimatorSet();
final Builder builder = mContentAnim.play(glowAnim).with(posAnim);
+
+ if (noRecentAppsFadeAnim != null) {
+ builder.with(noRecentAppsFadeAnim);
+ }
+
Drawable background = mScrimView.getBackground();
if (background != null) {
Animator bgAnim = ObjectAnimator.ofInt(background,
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
index 9bda158..5eb3639 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
@@ -83,6 +83,7 @@
private int mIconDpi;
private View mRecentsScrim;
private View mRecentsGlowView;
+ private View mRecentsNoApps;
private ViewGroup mRecentsContainer;
private Bitmap mDefaultThumbnailBackground;
@@ -373,8 +374,9 @@
mRecentsGlowView = findViewById(R.id.recents_glow);
- mRecentsScrim = (View) findViewById(R.id.recents_bg_protect);
- mChoreo = new Choreographer(this, mRecentsScrim, mRecentsGlowView, this);
+ mRecentsScrim = findViewById(R.id.recents_bg_protect);
+ mRecentsNoApps = findViewById(R.id.recents_no_apps);
+ mChoreo = new Choreographer(this, mRecentsScrim, mRecentsGlowView, mRecentsNoApps, this);
mRecentsDismissButton = findViewById(R.id.recents_dismiss_button);
mRecentsDismissButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
@@ -580,6 +582,9 @@
mThumbnailLoader.cancel(false);
mThumbnailLoader = null;
}
+ if (mRecentsNoApps != null) { // doesn't exist on large devices
+ mRecentsNoApps.setVisibility(View.INVISIBLE);
+ }
mActivityDescriptions = getRecentTasks();
for (ActivityDescription ad : mActivityDescriptions) {
ad.setThumbnail(mDefaultThumbnailBackground);
@@ -646,7 +651,11 @@
} else {
// Immediately hide this panel
if (DEBUG) Log.v(TAG, "Nothing to show");
- hide(false);
+ if (mRecentsNoApps != null) { // doesn't exist on large devices
+ mRecentsNoApps.setVisibility(View.VISIBLE);
+ } else {
+ hide(false);
+ }
}
}
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index e898aac..3e8d255 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -24,6 +24,7 @@
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
+import android.location.CountryDetector;
import android.net.Uri;
import android.os.SystemProperties;
import android.provider.Contacts;
@@ -1573,6 +1574,32 @@
}
/**
+ * Checks if a given number is an emergency number for the country that the user is in. The
+ * current country is determined using the CountryDetector.
+ *
+ * @param number the number to look up.
+ * @param context the specific context which the number should be checked against
+ * @return if a phone number is an emergency number for a local country, based on the
+ * CountryDetector.
+ * @see android.location.CountryDetector
+ * @hide
+ */
+ public static boolean isLocalEmergencyNumber(String number, Context context) {
+ String countryIso;
+ CountryDetector detector = (CountryDetector) context.getSystemService(
+ Context.COUNTRY_DETECTOR);
+ if (detector != null) {
+ countryIso = detector.detectCountry().getCountryIso();
+ } else {
+ Locale locale = context.getResources().getConfiguration().locale;
+ countryIso = locale.getCountry();
+ Log.w(LOG_TAG, "No CountryDetector; falling back to countryIso based on locale: "
+ + countryIso);
+ }
+ return isEmergencyNumber(number, countryIso);
+ }
+
+ /**
* isVoiceMailNumber: checks a given number against the voicemail
* number provided by the RIL and SIM card. The caller must have
* the READ_PHONE_STATE credential.
diff --git a/telephony/java/com/android/internal/telephony/CallerInfo.java b/telephony/java/com/android/internal/telephony/CallerInfo.java
index 7c37a65..6324550 100644
--- a/telephony/java/com/android/internal/telephony/CallerInfo.java
+++ b/telephony/java/com/android/internal/telephony/CallerInfo.java
@@ -254,9 +254,7 @@
// Change the callerInfo number ONLY if it is an emergency number
// or if it is the voicemail number. If it is either, take a
// shortcut and skip the query.
- Locale locale = context.getResources().getConfiguration().locale;
- String countryIso = getCurrentCountryIso(context, locale);
- if (PhoneNumberUtils.isEmergencyNumber(number, countryIso)) {
+ if (PhoneNumberUtils.isLocalEmergencyNumber(number, context)) {
return new CallerInfo().markAsEmergency(context);
} else if (PhoneNumberUtils.isVoiceMailNumber(number)) {
return new CallerInfo().markAsVoiceMail();
diff --git a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
index 17734ca..4912749 100644
--- a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
+++ b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
@@ -403,9 +403,7 @@
cw.number = number;
// check to see if these are recognized numbers, and use shortcuts if we can.
- CountryDetector detector = (CountryDetector) context.getSystemService(
- Context.COUNTRY_DETECTOR);
- if (PhoneNumberUtils.isEmergencyNumber(number, detector.detectCountry().getCountryIso())) {
+ if (PhoneNumberUtils.isLocalEmergencyNumber(number, context)) {
cw.event = EVENT_EMERGENCY_NUMBER;
} else if (PhoneNumberUtils.isVoiceMailNumber(number)) {
cw.event = EVENT_VOICEMAIL_NUMBER;
diff --git a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
index 42ea4f29..ae152fb 100644
--- a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
@@ -1019,7 +1019,7 @@
sendMessage(msg);
}
- public abstract boolean isAnyActiveDataConnections();
+ public abstract boolean isDisconnected();
protected void onSetUserDataEnabled(boolean enabled) {
synchronized (mDataEnabledLock) {
diff --git a/telephony/java/com/android/internal/telephony/ServiceStateTracker.java b/telephony/java/com/android/internal/telephony/ServiceStateTracker.java
index 01b807d..1b3ee1e 100644
--- a/telephony/java/com/android/internal/telephony/ServiceStateTracker.java
+++ b/telephony/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -403,7 +403,14 @@
public void powerOffRadioSafely(DataConnectionTracker dcTracker) {
synchronized (this) {
if (!mPendingRadioPowerOffAfterDataOff) {
- if (dcTracker.isAnyActiveDataConnections()) {
+ // To minimize race conditions we call cleanUpAllConnections on
+ // both if else paths instead of before this isDisconnected test.
+ if (dcTracker.isDisconnected()) {
+ // To minimize race conditions we do this after isDisconnected
+ dcTracker.cleanUpAllConnections(Phone.REASON_RADIO_TURNED_OFF);
+ if (DBG) log("Data disconnected, turn off radio right away.");
+ hangupAndPowerOff();
+ } else {
dcTracker.cleanUpAllConnections(Phone.REASON_RADIO_TURNED_OFF);
Message msg = Message.obtain(this);
msg.what = EVENT_SET_RADIO_POWER_OFF;
@@ -415,10 +422,6 @@
log("Cannot send delayed Msg, turn off radio right away.");
hangupAndPowerOff();
}
- } else {
- dcTracker.cleanUpAllConnections(Phone.REASON_RADIO_TURNED_OFF);
- if (DBG) log("Data disconnected, turn off radio right away.");
- hangupAndPowerOff();
}
}
}
diff --git a/telephony/java/com/android/internal/telephony/TelephonyProperties.java b/telephony/java/com/android/internal/telephony/TelephonyProperties.java
index abb4523..101dd55 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyProperties.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyProperties.java
@@ -182,4 +182,9 @@
* in commercial configuration.
*/
static final String PROPERTY_TEST_CSIM = "persist.radio.test-csim";
+
+ /**
+ * Set to true to indicate a test ims registration required.
+ */
+ static final String PROPERTY_IMS_REG_REQUIRED = "persist.radio.imsregrequired";
}
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaCallTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaCallTracker.java
index db19321..83efc51 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaCallTracker.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaCallTracker.java
@@ -190,7 +190,8 @@
String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false");
boolean isPhoneInEcmMode = inEcm.equals("true");
- boolean isEmergencyCall = PhoneNumberUtils.isEmergencyNumber(dialString);
+ boolean isEmergencyCall =
+ PhoneNumberUtils.isLocalEmergencyNumber(dialString, phone.getContext());
// Cancel Ecm timer if a second emergency call is originating in Ecm mode
if (isPhoneInEcmMode && isEmergencyCall) {
@@ -1059,7 +1060,7 @@
* Disable data call when emergency call is connected
*/
private void disableDataCallInEmergencyCall(String dialString) {
- if (PhoneNumberUtils.isEmergencyNumber(dialString)) {
+ if (PhoneNumberUtils.isLocalEmergencyNumber(dialString, phone.getContext())) {
if (Phone.DEBUG_PHONE) log("disableDataCallInEmergencyCall");
mIsInEmergencyCall = true;
phone.mDataConnectionTracker.setInternalDataEnabled(false);
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
index 078e51d..4dace82 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
@@ -946,8 +946,8 @@
}
@Override
- public boolean isAnyActiveDataConnections() {
- return (mState != State.IDLE);
+ public boolean isDisconnected() {
+ return ((mState == State.IDLE) || (mState == State.FAILED));
}
@Override
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
index dd8a60a..ebf5e65 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
@@ -1961,7 +1961,8 @@
return false;
}
- protected boolean isDisconnected() {
+ @Override
+ public boolean isDisconnected() {
for (ApnContext apnContext : mApnContexts.values()) {
if (!apnContext.isDisconnected()) {
// At least one context was not disconnected return false
@@ -2348,11 +2349,6 @@
}
@Override
- public boolean isAnyActiveDataConnections() {
- return isConnected();
- }
-
- @Override
protected void log(String s) {
Log.d(LOG_TAG, "[GsmDCT] "+ s);
}
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmMmiCode.java b/telephony/java/com/android/internal/telephony/gsm/GsmMmiCode.java
index 680b3cd..3799894 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmMmiCode.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmMmiCode.java
@@ -496,7 +496,7 @@
return false;
}
- if (PhoneNumberUtils.isEmergencyNumber(dialString)) {
+ if (PhoneNumberUtils.isLocalEmergencyNumber(dialString, phone.getContext())) {
return false;
} else {
return isShortCodeUSSD(dialString, phone);
diff --git a/test-runner/src/android/test/InstrumentationTestRunner.java b/test-runner/src/android/test/InstrumentationTestRunner.java
index c3d09ff..70cf89e 100644
--- a/test-runner/src/android/test/InstrumentationTestRunner.java
+++ b/test-runner/src/android/test/InstrumentationTestRunner.java
@@ -279,6 +279,7 @@
private static final String LOG_TAG = "InstrumentationTestRunner";
private final Bundle mResults = new Bundle();
+ private Bundle mArguments;
private AndroidTestRunner mTestRunner;
private boolean mDebug;
private boolean mJustCount;
@@ -292,6 +293,7 @@
@Override
public void onCreate(Bundle arguments) {
super.onCreate(arguments);
+ mArguments = arguments;
// Apk paths used to search for test classes when using TestSuiteBuilders.
String[] apkPaths =
@@ -379,6 +381,16 @@
start();
}
+ /**
+ * Get the Bundle object that contains the arguments
+ *
+ * @return the Bundle object
+ * @hide
+ */
+ public Bundle getBundle(){
+ return mArguments;
+ }
+
List<Predicate<TestMethod>> getBuilderRequirements() {
return new ArrayList<Predicate<TestMethod>>();
}