diff options
118 files changed, 2985 insertions, 1176 deletions
diff --git a/api/current.txt b/api/current.txt index 309e747a20f3..aa08bb4b54b2 100644 --- a/api/current.txt +++ b/api/current.txt @@ -596,10 +596,10 @@ package android { field public static final int layout_centerInParent = 16843151; // 0x101018f field public static final int layout_centerVertical = 16843153; // 0x1010191 field public static final int layout_column = 16843084; // 0x101014c - field public static final int layout_columnSpan = 16843646; // 0x101037e - field public static final int layout_columnWeight = 16843647; // 0x101037f + field public static final int layout_columnSpan = 16843645; // 0x101037d field public static final int layout_gravity = 16842931; // 0x10100b3 field public static final int layout_height = 16842997; // 0x10100f5 + field public static final int layout_heightSpec = 16843647; // 0x101037f field public static final int layout_margin = 16842998; // 0x10100f6 field public static final int layout_marginBottom = 16843002; // 0x10100fa field public static final int layout_marginEnd = 16843675; // 0x101039b @@ -609,13 +609,13 @@ package android { field public static final int layout_marginTop = 16843000; // 0x10100f8 field public static final int layout_row = 16843643; // 0x101037b field public static final int layout_rowSpan = 16843644; // 0x101037c - field public static final int layout_rowWeight = 16843645; // 0x101037d field public static final int layout_scale = 16843155; // 0x1010193 field public static final int layout_span = 16843085; // 0x101014d field public static final int layout_toLeftOf = 16843138; // 0x1010182 field public static final int layout_toRightOf = 16843139; // 0x1010183 field public static final int layout_weight = 16843137; // 0x1010181 field public static final int layout_width = 16842996; // 0x10100f4 + field public static final int layout_widthSpec = 16843646; // 0x101037e field public static final int layout_x = 16843135; // 0x101017f field public static final int layout_y = 16843136; // 0x1010180 field public static final int left = 16843181; // 0x10101ad @@ -3453,6 +3453,7 @@ package android.app { field public static final java.lang.String SUGGEST_COLUMN_INTENT_DATA = "suggest_intent_data"; field public static final java.lang.String SUGGEST_COLUMN_INTENT_DATA_ID = "suggest_intent_data_id"; field public static final java.lang.String SUGGEST_COLUMN_INTENT_EXTRA_DATA = "suggest_intent_extra_data"; + field public static final java.lang.String SUGGEST_COLUMN_LAST_ACCESS_HINT = "suggest_last_access_hint"; field public static final java.lang.String SUGGEST_COLUMN_QUERY = "suggest_intent_query"; field public static final java.lang.String SUGGEST_COLUMN_SHORTCUT_ID = "suggest_shortcut_id"; field public static final java.lang.String SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING = "suggest_spinner_while_refreshing"; @@ -22074,6 +22075,7 @@ package android.view { method public boolean willNotDraw(); field public static android.util.Property ALPHA; field protected static int DEFAULT_TEXT_DIRECTION; + field protected static float DEFAULT_TEXT_DIRECTION_CHAR_COUNT_THRESHOLD; field public static final int DRAWING_CACHE_QUALITY_AUTO = 0; // 0x0 field public static final int DRAWING_CACHE_QUALITY_HIGH = 1048576; // 0x100000 field public static final int DRAWING_CACHE_QUALITY_LOW = 524288; // 0x80000 @@ -22263,6 +22265,7 @@ package android.view { method public static deprecated int getTouchSlop(); method public static deprecated int getWindowTouchSlop(); method public static long getZoomControlsTimeout(); + method public boolean hasPermanentMenuKey(); } public class ViewDebug { @@ -24890,9 +24893,9 @@ package android.widget { } public class GridLayout extends android.view.ViewGroup { - ctor public GridLayout(android.content.Context); ctor public GridLayout(android.content.Context, android.util.AttributeSet, int); ctor public GridLayout(android.content.Context, android.util.AttributeSet); + ctor public GridLayout(android.content.Context); method public int getAlignmentMode(); method public int getColumnCount(); method public int getOrientation(); @@ -24912,8 +24915,11 @@ package android.widget { field public static final int ALIGN_MARGINS = 1; // 0x1 field public static final android.widget.GridLayout.Alignment BASELINE; field public static final android.widget.GridLayout.Alignment BOTTOM; + field public static final android.widget.GridLayout.Spec CAN_SHRINK; + field public static final android.widget.GridLayout.Spec CAN_STRETCH; field public static final android.widget.GridLayout.Alignment CENTER; field public static final android.widget.GridLayout.Alignment FILL; + field public static final android.widget.GridLayout.Spec FIXED; field public static final int HORIZONTAL = 0; // 0x0 field public static final android.widget.GridLayout.Alignment LEFT; field public static final android.widget.GridLayout.Alignment RIGHT; @@ -24940,9 +24946,13 @@ package android.widget { ctor public GridLayout.LayoutParams(android.content.Context, android.util.AttributeSet); method public void setGravity(int); field public android.widget.GridLayout.Group columnGroup; - field public float columnWeight; + field public android.widget.GridLayout.Spec heightSpec; field public android.widget.GridLayout.Group rowGroup; - field public float rowWeight; + field public android.widget.GridLayout.Spec widthSpec; + } + + public static abstract class GridLayout.Spec { + ctor public GridLayout.Spec(); } public class GridView extends android.widget.AbsListView { diff --git a/cmds/keystore/keystore.cpp b/cmds/keystore/keystore.cpp index 1c1f37a34c7b..4b4b9b9d4adb 100644 --- a/cmds/keystore/keystore.cpp +++ b/cmds/keystore/keystore.cpp @@ -712,7 +712,6 @@ static struct user { {AID_VPN, AID_SYSTEM, GET}, {AID_WIFI, AID_SYSTEM, GET}, {AID_ROOT, AID_SYSTEM, GET}, - {AID_KEYCHAIN, AID_SYSTEM, TEST | GET | SAW}, {~0, ~0, TEST | GET | INSERT | DELETE | EXIST | SAW}, }; diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java index 41eea2e83b31..42eda0212e01 100644 --- a/core/java/android/app/SearchDialog.java +++ b/core/java/android/app/SearchDialog.java @@ -188,8 +188,9 @@ public class SearchDialog extends Dialog { mSearchPlate = mSearchView.findViewById(com.android.internal.R.id.search_plate); mWorkingSpinner = getContext().getResources(). getDrawable(com.android.internal.R.drawable.search_spinner); - mSearchAutoComplete.setCompoundDrawablesWithIntrinsicBounds( - null, null, mWorkingSpinner, null); + // TODO: Restore the spinner for slow suggestion lookups + // mSearchAutoComplete.setCompoundDrawablesWithIntrinsicBounds( + // null, null, mWorkingSpinner, null); setWorking(false); // pre-hide all the extraneous elements diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java index 85a2fa831f65..7274362e77b5 100644 --- a/core/java/android/app/SearchManager.java +++ b/core/java/android/app/SearchManager.java @@ -329,6 +329,15 @@ public class SearchManager public final static String SUGGEST_COLUMN_FLAGS = "suggest_flags"; /** + * Column name for suggestions cursor. <i>Optional.</i> This column may be + * used to specify the time in (@link System#currentTimeMillis + * System.currentTImeMillis()} (wall time in UTC) when an item was last + * accessed within the results-providing application. If set, this may be + * used to show more-recently-used items first. + */ + public final static String SUGGEST_COLUMN_LAST_ACCESS_HINT = "suggest_last_access_hint"; + + /** * Column value for suggestion column {@link #SUGGEST_COLUMN_SHORTCUT_ID} when a suggestion * should not be stored as a shortcut in global search. */ diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl index ae9aa05e0bce..054825024b9a 100644 --- a/core/java/android/net/INetworkStatsService.aidl +++ b/core/java/android/net/INetworkStatsService.aidl @@ -33,4 +33,7 @@ interface INetworkStatsService { /** Return usage summary per UID for traffic that matches template. */ NetworkStats getSummaryForAllUid(in NetworkTemplate template, long start, long end, boolean includeTags); + /** Force update of statistics. */ + void forceUpdate(); + } diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java index ff6e220cac41..dd2945ca18af 100644 --- a/core/java/android/net/NetworkStatsHistory.java +++ b/core/java/android/net/NetworkStatsHistory.java @@ -279,10 +279,17 @@ public class NetworkStatsHistory implements Parcelable { return (long) (start + (r.nextFloat() * (end - start))); } - public void dump(String prefix, PrintWriter pw) { + public void dump(String prefix, PrintWriter pw, boolean fullHistory) { pw.print(prefix); pw.print("NetworkStatsHistory: bucketDuration="); pw.println(bucketDuration); - for (int i = 0; i < bucketCount; i++) { + + final int start = fullHistory ? 0 : Math.max(0, bucketCount - 32); + if (start > 0) { + pw.print(prefix); + pw.print(" (omitting "); pw.print(start); pw.println(" buckets)"); + } + + for (int i = start; i < bucketCount; i++) { pw.print(prefix); pw.print(" bucketStart="); pw.print(bucketStart[i]); pw.print(" rx="); pw.print(rx[i]); @@ -293,7 +300,7 @@ public class NetworkStatsHistory implements Parcelable { @Override public String toString() { final CharArrayWriter writer = new CharArrayWriter(); - dump("", new PrintWriter(writer)); + dump("", new PrintWriter(writer), false); return writer.toString(); } diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl index c9b6121adcf9..fcf479631b03 100644 --- a/core/java/android/os/INetworkManagementService.aidl +++ b/core/java/android/os/INetworkManagementService.aidl @@ -241,6 +241,4 @@ interface INetworkManagementService */ int getInterfaceTxThrottle(String iface); - void setBandwidthControlEnabled(boolean enabled); - } diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index d475f36df9bb..05e39ac55a89 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -92,12 +92,6 @@ public class Process { public static final int SDCARD_RW_GID = 1015; /** - * Defines the UID for the KeyChain service. - * @hide - */ - public static final int KEYCHAIN_UID = 1020; - - /** * Defines the UID/GID for the NFC service process. * @hide */ diff --git a/core/java/android/provider/VoicemailContract.java b/core/java/android/provider/VoicemailContract.java index ab0cb505eb2d..d99c7605bebe 100644 --- a/core/java/android/provider/VoicemailContract.java +++ b/core/java/android/provider/VoicemailContract.java @@ -52,18 +52,27 @@ public class VoicemailContract { /** The authority used by the voicemail provider. */ public static final String AUTHORITY = "com.android.voicemail"; - - /** URI to insert/retrieve all voicemails. */ + /** + * URI to insert/retrieve all voicemails. + * @deprecated + */ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/voicemail"); - /** URI to insert/retrieve voicemails by a given voicemail source. */ + /** + * URI to insert/retrieve voicemails by a given voicemail source. + * @deprecated + */ public static final Uri CONTENT_URI_SOURCE = Uri.parse("content://" + AUTHORITY + "/voicemail/source/"); + /** + * Parameter key used in the URI to specify the voicemail source package name. + * <p> This field must be set in all requests that originate from a voicemail source. + */ + public static final String PARAM_KEY_SOURCE_PACKAGE = "source_package"; // TODO: Move ACTION_NEW_VOICEMAIL to the Intent class. /** Broadcast intent when a new voicemail record is inserted. */ public static final String ACTION_NEW_VOICEMAIL = "android.intent.action.NEW_VOICEMAIL"; - /** * Extra included in {@value Intent#ACTION_PROVIDER_CHANGED} and * {@value #ACTION_NEW_VOICEMAIL} broadcast intents to indicate if the receiving @@ -71,15 +80,27 @@ public class VoicemailContract { */ public static final String EXTRA_SELF_CHANGE = "com.android.voicemail.extra.SELF_CHANGE"; - /** The mime type for a collection of voicemails. */ - public static final String DIR_TYPE = - "vnd.android.cursor.dir/voicemails"; + /** + * The mime type for a collection of voicemails. + * @deprecated */ + public static final String DIR_TYPE = "vnd.android.cursor.dir/voicemails"; + /** Defines fields exposed through the /voicemail path of this content provider. */ public static final class Voicemails implements BaseColumns { /** Not instantiable. */ private Voicemails() { } + /** URI to insert/retrieve voicemails by a given voicemail source. */ + public static final Uri CONTENT_URI = + Uri.parse("content://" + AUTHORITY + "/voicemail"); + /** URI to insert/retrieve voicemails by a given voicemail source. */ + public static final Uri CONTENT_URI_SOURCE = + Uri.parse("content://" + AUTHORITY + "/voicemail/source/"); + + /** The mime type for a collection of voicemails. */ + public static final String DIR_TYPE = "vnd.android.cursor.dir/voicemails"; + /** * Phone number of the voicemail sender. * <P>Type: TEXT</P> @@ -143,5 +164,101 @@ public class VoicemailContract { * @hide */ public static final String _DATA = "_data"; + + /** + * A convenience method to build voicemail URI specific to a source package by appending + * {@link VoicemailContract#PARAM_KEY_SOURCE_PACKAGE} param to the base URI. + */ + public static Uri buildSourceUri(String packageName) { + return Voicemails.CONTENT_URI.buildUpon() + .appendQueryParameter(PARAM_KEY_SOURCE_PACKAGE, packageName).build(); + } + } + + /** Defines fields exposed through the /status path of this content provider. */ + public static final class Status implements BaseColumns { + /** URI to insert/retrieve status of voicemail source. */ + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/status"); + /** The mime type for a collection of voicemail source statuses. */ + public static final String DIR_TYPE = "vnd.android.cursor.dir/voicemail.source.status"; + /** The mime type for a collection of voicemails. */ + public static final String ITEM_TYPE = "vnd.android.cursor.item/voicemail.source.status"; + + /** Not instantiable. */ + private Status() { + } + /** + * The package name of the voicemail source. There can only be a one entry per source. + * <P>Type: TEXT</P> + */ + public static final String SOURCE_PACKAGE = "source_package"; + /** + * The URI to call to invoke source specific voicemail settings screen. On a user request + * to setup voicemail an intent with action VIEW with this URI will be fired by the system. + * <P>Type: TEXT</P> + */ + public static final String SETTINGS_URI = "settings_uri"; + /** + * The URI to call when the user requests to directly access the voicemail from the remote + * server. In case of an IVR voicemail system this is typically set to the the voicemail + * number specified using a tel:/ URI. + * <P>Type: TEXT</P> + */ + public static final String VOICEMAIL_ACCESS_URI = "voicemail_access_uri"; + /** + * The configuration state of the voicemail source. + * <P> Possible values: + * {@link #CONFIGURATION_STATE_OK}, + * {@link #CONFIGURATION_STATE_NOT_CONFIGURED}, + * {@link #CONFIGURATION_STATE_CAN_BE_CONFIGURED} + * <P>Type: INTEGER</P> + */ + public static final String CONFIGURATION_STATE = "configuration_state"; + public static final int CONFIGURATION_STATE_OK = 0; + public static final int CONFIGURATION_STATE_NOT_CONFIGURED = 1; + /** + * This state must be used when the source has verified that the current user can be + * upgraded to visual voicemail and would like to show a set up invitation message. + */ + public static final int CONFIGURATION_STATE_CAN_BE_CONFIGURED = 2; + /** + * The data channel state of the voicemail source. This the channel through which the source + * pulls voicemail data from a remote server. + * <P> Possible values: + * {@link #DATA_CHANNEL_STATE_OK}, + * {@link #DATA_CHANNEL_STATE_NO_CONNECTION} + * </P> + * <P>Type: INTEGER</P> + */ + public static final String DATA_CHANNEL_STATE = "data_channel_state"; + public static final int DATA_CHANNEL_STATE_OK = 0; + public static final int DATA_CHANNEL_STATE_NO_CONNECTION = 1; + /** + * The notification channel state of the voicemail source. This is the channel through which + * the source gets notified of new voicemails on the remote server. + * <P> Possible values: + * {@link #NOTIFICATION_CHANNEL_STATE_OK}, + * {@link #NOTIFICATION_CHANNEL_STATE_NO_CONNECTION}, + * {@link #NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING} + * </P> + * <P>Type: INTEGER</P> + */ + public static final String NOTIFICATION_CHANNEL_STATE = "notification_channel_state"; + public static final int NOTIFICATION_CHANNEL_STATE_OK = 0; + public static final int NOTIFICATION_CHANNEL_STATE_NO_CONNECTION = 1; + /** + * Use this state when the notification can only tell that there are pending messages on + * the server but no details of the sender/time etc are known. + */ + public static final int NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING = 2; + + /** + * A convenience method to build status URI specific to a source package by appending + * {@link VoicemailContract#PARAM_KEY_SOURCE_PACKAGE} param to the base URI. + */ + public static Uri buildSourceUri(String packageName) { + return Status.CONTENT_URI.buildUpon() + .appendQueryParameter(PARAM_KEY_SOURCE_PACKAGE, packageName).build(); + } } } diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java index ca2212cda581..8a6fdb4fb325 100644 --- a/core/java/android/server/BluetoothA2dpService.java +++ b/core/java/android/server/BluetoothA2dpService.java @@ -517,6 +517,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); intent.putExtra(BluetoothProfile.EXTRA_STATE, state); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); mContext.sendBroadcast(intent, BLUETOOTH_PERM); if (DBG) log("A2DP state : device: " + device + " State:" + prevState + "->" + state); @@ -530,6 +531,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); intent.putExtra(BluetoothProfile.EXTRA_STATE, state); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); mContext.sendBroadcast(intent, BLUETOOTH_PERM); if (DBG) log("A2DP Playing state : device: " + device + " State:" + prevState + "->" + state); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index bb5c95497989..12458987a63a 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -2523,18 +2523,26 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit public static final int TEXT_DIRECTION_ANY_RTL = 2; /** + * Text direction is the same as the one held by a 60% majority of the characters. If there is + * no majority then the paragraph direction is the resolved layout direction of the View. + * + * @hide + */ + public static final int TEXT_DIRECTION_CHAR_COUNT = 3; + + /** * Text direction is forced to LTR. * * @hide */ - public static final int TEXT_DIRECTION_LTR = 3; + public static final int TEXT_DIRECTION_LTR = 4; /** * Text direction is forced to RTL. * * @hide */ - public static final int TEXT_DIRECTION_RTL = 4; + public static final int TEXT_DIRECTION_RTL = 5; /** * Default text direction is inherited @@ -2542,6 +2550,11 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit protected static int DEFAULT_TEXT_DIRECTION = TEXT_DIRECTION_INHERIT; /** + * Default threshold for "char count" heuristic. + */ + protected static float DEFAULT_TEXT_DIRECTION_CHAR_COUNT_THRESHOLD = 0.6f; + + /** * The text direction that has been defined by {@link #setTextDirection(int)}. * * {@hide} @@ -2551,6 +2564,7 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit @ViewDebug.IntToString(from = TEXT_DIRECTION_INHERIT, to = "INHERIT"), @ViewDebug.IntToString(from = TEXT_DIRECTION_FIRST_STRONG, to = "FIRST_STRONG"), @ViewDebug.IntToString(from = TEXT_DIRECTION_ANY_RTL, to = "ANY_RTL"), + @ViewDebug.IntToString(from = TEXT_DIRECTION_CHAR_COUNT, to = "CHAR_COUNT"), @ViewDebug.IntToString(from = TEXT_DIRECTION_LTR, to = "LTR"), @ViewDebug.IntToString(from = TEXT_DIRECTION_RTL, to = "RTL") }) @@ -11969,7 +11983,7 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit mPrivateFlags |= FORCE_LAYOUT; mPrivateFlags |= INVALIDATED; - if (mLayoutParams != null) { + if (mLayoutParams != null && mParent != null) { mLayoutParams.resolveWithDirection(getResolvedLayoutDirection()); } @@ -12996,6 +13010,7 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit * {@link #TEXT_DIRECTION_INHERIT}, * {@link #TEXT_DIRECTION_FIRST_STRONG} * {@link #TEXT_DIRECTION_ANY_RTL}, + * {@link #TEXT_DIRECTION_CHAR_COUNT}, * {@link #TEXT_DIRECTION_LTR}, * {@link #TEXT_DIRECTION_RTL}, * @@ -13013,6 +13028,7 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit * {@link #TEXT_DIRECTION_INHERIT}, * {@link #TEXT_DIRECTION_FIRST_STRONG} * {@link #TEXT_DIRECTION_ANY_RTL}, + * {@link #TEXT_DIRECTION_CHAR_COUNT}, * {@link #TEXT_DIRECTION_LTR}, * {@link #TEXT_DIRECTION_RTL}, * diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java index f3a5050001ea..dbcbd6e5ae1f 100644 --- a/core/java/android/view/ViewConfiguration.java +++ b/core/java/android/view/ViewConfiguration.java @@ -19,6 +19,8 @@ package android.view; import android.app.AppGlobals; import android.content.Context; import android.content.res.Configuration; +import android.content.res.Resources; +import android.os.RemoteException; import android.provider.Settings; import android.util.DisplayMetrics; import android.util.SparseArray; @@ -219,6 +221,9 @@ public class ViewConfiguration { private final int mOverscrollDistance; private final int mOverflingDistance; + private boolean sHasPermanentMenuKey; + private boolean sHasPermanentMenuKeySet; + private static final SparseArray<ViewConfiguration> sConfigurations = new SparseArray<ViewConfiguration>(2); @@ -254,11 +259,12 @@ public class ViewConfiguration { * @see android.util.DisplayMetrics */ private ViewConfiguration(Context context) { - final DisplayMetrics metrics = context.getResources().getDisplayMetrics(); + final Resources res = context.getResources(); + final DisplayMetrics metrics = res.getDisplayMetrics(); + final Configuration config = res.getConfiguration(); final float density = metrics.density; final float sizeAndDensity; - if (context.getResources().getConfiguration().isLayoutSizeAtLeast( - Configuration.SCREENLAYOUT_SIZE_XLARGE)) { + if (config.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_XLARGE)) { sizeAndDensity = density * 1.5f; } else { sizeAndDensity = density; @@ -280,6 +286,17 @@ public class ViewConfiguration { mOverscrollDistance = (int) (sizeAndDensity * OVERSCROLL_DISTANCE + 0.5f); mOverflingDistance = (int) (sizeAndDensity * OVERFLING_DISTANCE + 0.5f); + + if (!sHasPermanentMenuKeySet) { + IWindowManager wm = Display.getWindowManager(); + try { + sHasPermanentMenuKey = wm.canStatusBarHide() && !res.getBoolean( + com.android.internal.R.bool.config_showNavigationBar); + sHasPermanentMenuKeySet = true; + } catch (RemoteException ex) { + sHasPermanentMenuKey = false; + } + } } /** @@ -640,4 +657,20 @@ public class ViewConfiguration { public static float getScrollFriction() { return SCROLL_FRICTION; } + + /** + * Report if the device has a permanent menu key available to the user. + * + * <p>As of Android 3.0, devices may not have a permanent menu key available. + * Apps should use the action bar to present menu options to users. + * However, there are some apps where the action bar is inappropriate + * or undesirable. This method may be used to detect if a menu key is present. + * If not, applications should provide another on-screen affordance to access + * functionality. + * + * @return true if a permanent menu key is present, false otherwise. + */ + public boolean hasPermanentMenuKey() { + return sHasPermanentMenuKey; + } } diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index da88fbb8fff3..9f4ecbc47c85 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -5036,6 +5036,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager // Pass down the hierarchy the following text direction values case TEXT_DIRECTION_FIRST_STRONG: case TEXT_DIRECTION_ANY_RTL: + case TEXT_DIRECTION_CHAR_COUNT: case TEXT_DIRECTION_LTR: case TEXT_DIRECTION_RTL: resolvedTextDirection = mTextDirection; diff --git a/core/java/android/webkit/HTML5VideoFullScreen.java b/core/java/android/webkit/HTML5VideoFullScreen.java index 11ab0d778043..0ea27a0b9f5e 100644 --- a/core/java/android/webkit/HTML5VideoFullScreen.java +++ b/core/java/android/webkit/HTML5VideoFullScreen.java @@ -107,6 +107,7 @@ public class HTML5VideoFullScreen extends HTML5VideoView // After we return from this we can't use the surface any more. // The current Video View will be destroy when we play a new video. pauseAndDispatch(mProxy); + mPlayer.release(); mSurfaceHolder = null; if (mMediaController != null) { mMediaController.hide(); @@ -226,6 +227,10 @@ public class HTML5VideoFullScreen extends HTML5VideoView mProxy.getWebView().getViewManager().showAll(); mProxy = null; + + // Don't show the controller after exiting the full screen. + mMediaController = null; + mCurrentState = STATE_RELEASED; } }; diff --git a/core/java/android/webkit/HTML5VideoView.java b/core/java/android/webkit/HTML5VideoView.java index 5983a4444e96..67660b86af02 100644 --- a/core/java/android/webkit/HTML5VideoView.java +++ b/core/java/android/webkit/HTML5VideoView.java @@ -34,6 +34,7 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener { static final int STATE_NOTPREPARED = 1; static final int STATE_PREPARED = 2; static final int STATE_PLAYING = 3; + static final int STATE_RELEASED = 4; protected int mCurrentState; protected HTML5VideoViewProxy mProxy; @@ -84,7 +85,7 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener { } public void pause() { - if (mCurrentState == STATE_PREPARED && mPlayer.isPlaying()) { + if (isPlaying()) { mPlayer.pause(); } else if (mCurrentState == STATE_NOTPREPARED) { mPauseDuringPreparing = true; @@ -120,11 +121,18 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener { } public boolean isPlaying() { - return mPlayer.isPlaying(); + if (mCurrentState == STATE_PREPARED) { + return mPlayer.isPlaying(); + } else { + return false; + } } public void release() { - mPlayer.release(); + if (mCurrentState != STATE_RELEASED) { + mPlayer.release(); + } + mCurrentState = STATE_RELEASED; } public void stopPlayback() { @@ -228,7 +236,7 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener { public int getCurrentState() { - if (mPlayer.isPlaying()) { + if (isPlaying()) { return STATE_PLAYING; } else { return mCurrentState; diff --git a/core/java/android/webkit/L10nUtils.java b/core/java/android/webkit/L10nUtils.java index 5b4fb1df72de..4c42cde5b693 100644 --- a/core/java/android/webkit/L10nUtils.java +++ b/core/java/android/webkit/L10nUtils.java @@ -70,7 +70,11 @@ public class L10nUtils { com.android.internal.R.string.autofill_expiration_month_re, // IDS_AUTOFILL_EXPIRATION_MONTH_RE com.android.internal.R.string.autofill_expiration_date_re, // IDS_AUTOFILL_EXPIRATION_DATE_RE com.android.internal.R.string.autofill_card_ignored_re, // IDS_AUTOFILL_CARD_IGNORED_RE - com.android.internal.R.string.autofill_fax_re // IDS_AUTOFILL_FAX_RE + com.android.internal.R.string.autofill_fax_re, // IDS_AUTOFILL_FAX_RE + com.android.internal.R.string.autofill_country_code_re, // IDS_AUTOFILL_COUNTRY_CODE_RE + com.android.internal.R.string.autofill_area_code_notext_re, // IDS_AUTOFILL_AREA_CODE_NOTEXT_RE + com.android.internal.R.string.autofill_phone_prefix_separator_re, // IDS_AUTOFILL_PHONE_PREFIX_SEPARATOR_RE + com.android.internal.R.string.autofill_phone_suffix_separator_re // IDS_AUTOFILL_PHONE_SUFFIX_SEPARATOR_RE }; private static Context mApplicationContext; diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java index 15702244596f..7c0470e17434 100644 --- a/core/java/android/widget/GridLayout.java +++ b/core/java/android/widget/GridLayout.java @@ -24,6 +24,7 @@ import android.graphics.Paint; import android.graphics.Rect; import android.util.AttributeSet; import android.util.Log; +import android.util.Pair; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; @@ -33,7 +34,6 @@ import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -67,7 +67,7 @@ import static java.lang.Math.min; * * <h4>Default Cell Assignment</h4> * - * If no child specifies the row and column indices of the cell it + * If a child does not specify the row and column indices of the cell it * wishes to occupy, GridLayout assigns cell locations automatically using its: * {@link GridLayout#setOrientation(int) orientation}, * {@link GridLayout#setRowCount(int) rowCount} and @@ -94,8 +94,8 @@ import static java.lang.Math.min; * * Like {@link LinearLayout}, a child's ability to stretch is controlled * using <em>weights</em>, which are specified using the - * {@link GridLayout.LayoutParams#rowWeight rowWeight} and - * {@link GridLayout.LayoutParams#columnWeight columnWeight} layout parameters. + * {@link GridLayout.LayoutParams#widthSpec widthSpec} and + * {@link GridLayout.LayoutParams#heightSpec heightSpec} layout parameters. * <p> * <p> * See {@link GridLayout.LayoutParams} for a full description of the @@ -171,9 +171,7 @@ public class GridLayout extends ViewGroup { private static final String TAG = GridLayout.class.getName(); private static final boolean DEBUG = false; private static final double GOLDEN_RATIO = (1 + Math.sqrt(5)) / 2; - private static final int MIN = 0; private static final int PRF = 1; - private static final int MAX = 2; // Defaults @@ -184,6 +182,7 @@ public class GridLayout extends ViewGroup { private static final int DEFAULT_ALIGNMENT_MODE = ALIGN_MARGINS; // todo remove this private static final int DEFAULT_CONTAINER_MARGIN = 20; + private static final int MAX_SIZE = 100000; // TypedArray indices @@ -205,36 +204,16 @@ public class GridLayout extends ViewGroup { private int mAlignmentMode = DEFAULT_ALIGNMENT_MODE; private int mDefaultGravity = Gravity.NO_GRAVITY; - /* package */ boolean accommodateBothMinAndMax = false; - // Constructors /** * {@inheritDoc} */ - public GridLayout(Context context) { - this(context, null, 0); - } - - /** - * {@inheritDoc} - */ public GridLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); if (DEBUG) { setWillNotDraw(false); } - processAttributes(context, attrs); - } - - /** - * {@inheritDoc} - */ - public GridLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - private void processAttributes(Context context, AttributeSet attrs) { TypedArray a = context.obtainStyledAttributes(attrs, styleable.GridLayout); try { setRowCount(a.getInt(ROW_COUNT, DEFAULT_COUNT)); @@ -249,6 +228,20 @@ public class GridLayout extends ViewGroup { } } + /** + * {@inheritDoc} + */ + public GridLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + /** + * {@inheritDoc} + */ + public GridLayout(Context context) { + this(context, null); + } + // Implementation /** @@ -527,11 +520,10 @@ public class GridLayout extends ViewGroup { return result; } - private static int sum(float[] a) { - int result = 0; - for (int i = 0, length = a.length; i < length; i++) { - result += a[i]; - } + private 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; } @@ -603,13 +595,13 @@ public class GridLayout extends ViewGroup { if (isGone(c)) continue; LayoutParams lp = getLayoutParams1(c); - Group colGroup = lp.columnGroup; - Interval cols = colGroup.span; - int colSpan = cols.size(); + final Group colGroup = lp.columnGroup; + final Interval cols = colGroup.span; + final int colSpan = cols.size(); - Group rowGroup = lp.rowGroup; - Interval rows = rowGroup.span; - int rowSpan = rows.size(); + final Group rowGroup = lp.rowGroup; + final Interval rows = rowGroup.span; + final int rowSpan = rows.size(); if (horizontal) { row = valueIfDefined2(rows.min, row); @@ -700,8 +692,8 @@ public class GridLayout extends ViewGroup { } private void drawRectangle(Canvas graphics, int x1, int y1, int x2, int y2, Paint paint) { - // x2 = x2 - 1; - // y2 = y2 - 1; + x2 = x2 - 1; + y2 = y2 - 1; graphics.drawLine(x1, y1, x1, y2, paint); graphics.drawLine(x1, y1, x2, y1, paint); graphics.drawLine(x1, y2, x2, y2, paint); @@ -734,9 +726,9 @@ public class GridLayout extends ViewGroup { drawLine(canvas, 0, y, width - 1, y, paint); } } + // Draw bounds paint.setColor(Color.BLUE); - for (int i = 0; i < getChildCount(); i++) { View c = getChildAt(i); drawRectangle(canvas, @@ -748,7 +740,6 @@ public class GridLayout extends ViewGroup { // Draw margins paint.setColor(Color.YELLOW); - for (int i = 0; i < getChildCount(); i++) { View c = getChildAt(i); drawRectangle(canvas, @@ -819,11 +810,11 @@ public class GridLayout extends ViewGroup { protected void onMeasure(int widthSpec, int heightSpec) { measureChildrenWithMargins(widthSpec, heightSpec); - int computedWidth = getPaddingLeft() + mHorizontalAxis.getMin() + getPaddingRight(); - int computedHeight = getPaddingTop() + mVerticalAxis.getMin() + getPaddingBottom(); + int width = getPaddingLeft() + mHorizontalAxis.getMeasure(widthSpec) + getPaddingRight(); + int height = getPaddingTop() + mVerticalAxis.getMeasure(heightSpec) + getPaddingBottom(); - int measuredWidth = Math.max(computedWidth, getSuggestedMinimumWidth()); - int measuredHeight = Math.max(computedHeight, getSuggestedMinimumHeight()); + int measuredWidth = Math.max(width, getSuggestedMinimumWidth()); + int measuredHeight = Math.max(height, getSuggestedMinimumHeight()); setMeasuredDimension( resolveSizeAndState(measuredWidth, widthSpec, 0), @@ -834,12 +825,12 @@ public class GridLayout extends ViewGroup { return (alignment == UNDEFINED) ? 0 : alignment; } - private int getMeasurement(View c, boolean horizontal, int measurementType) { + private int getMeasurement(View c, boolean horizontal) { return horizontal ? c.getMeasuredWidth() : c.getMeasuredHeight(); } - private int getMeasurementIncludingMargin(View c, boolean horizontal, int measurementType) { - int result = getMeasurement(c, horizontal, measurementType); + private int getMeasurementIncludingMargin(View c, boolean horizontal) { + int result = getMeasurement(c, horizontal); if (mAlignmentMode == ALIGN_MARGINS) { return result + getTotalMargin(c, horizontal); } @@ -889,17 +880,17 @@ public class GridLayout extends ViewGroup { Interval colSpan = columnGroup.span; Interval rowSpan = rowGroup.span; - int x1 = mHorizontalAxis.getLocationIncludingMargin(c, true, colSpan.min); - int y1 = mVerticalAxis.getLocationIncludingMargin(c, true, rowSpan.min); + int x1 = mHorizontalAxis.getLocationIncludingMargin(true, colSpan.min); + int y1 = mVerticalAxis.getLocationIncludingMargin(true, rowSpan.min); - int x2 = mHorizontalAxis.getLocationIncludingMargin(c, false, colSpan.max); - int y2 = mVerticalAxis.getLocationIncludingMargin(c, false, rowSpan.max); + int x2 = mHorizontalAxis.getLocationIncludingMargin(false, colSpan.max); + int y2 = mVerticalAxis.getLocationIncludingMargin(false, rowSpan.max); int cellWidth = x2 - x1; int cellHeight = y2 - y1; - int pWidth = getMeasurement(c, true, PRF); - int pHeight = getMeasurement(c, false, PRF); + int pWidth = getMeasurement(c, true); + int pHeight = getMeasurement(c, false); Alignment hAlign = columnGroup.alignment; Alignment vAlign = rowGroup.alignment; @@ -910,9 +901,8 @@ public class GridLayout extends ViewGroup { Bounds rowBounds = mVerticalAxis.getGroupBounds().getValue(i); // Gravity offsets: the location of the alignment group relative to its cell group. - int type = PRF; - int c2ax = protect(hAlign.getAlignmentValue(null, cellWidth - colBounds.size(), type)); - int c2ay = protect(vAlign.getAlignmentValue(null, cellHeight - rowBounds.size(), type)); + int c2ax = protect(hAlign.getAlignmentValue(null, cellWidth - colBounds.size(true))); + int c2ay = protect(vAlign.getAlignmentValue(null, cellHeight - rowBounds.size(true))); if (mAlignmentMode == ALIGN_MARGINS) { int leftMargin = getMargin(c, true, true); @@ -925,8 +915,8 @@ public class GridLayout extends ViewGroup { int mHeight = topMargin + pHeight + bottomMargin; // Alignment offsets: the location of the view relative to its alignment group. - int a2vx = colBounds.getOffset(c, hAlign, type, mWidth); - int a2vy = rowBounds.getOffset(c, vAlign, type, mHeight); + int a2vx = colBounds.getOffset(c, hAlign, mWidth); + int a2vy = rowBounds.getOffset(c, vAlign, mHeight); dx = c2ax + a2vx + leftMargin; dy = c2ay + a2vy + topMargin; @@ -935,13 +925,14 @@ public class GridLayout extends ViewGroup { cellHeight -= topMargin + bottomMargin; } else { // Alignment offsets: the location of the view relative to its alignment group. - int a2vx = colBounds.getOffset(c, hAlign, type, pWidth); - int a2vy = rowBounds.getOffset(c, vAlign, type, pHeight); + int a2vx = colBounds.getOffset(c, hAlign, pWidth); + int a2vy = rowBounds.getOffset(c, vAlign, pHeight); dx = c2ax + a2vx; dy = c2ay + a2vy; } + int type = PRF; int width = hAlign.getSizeInCell(c, pWidth, cellWidth, type); int height = vAlign.getSizeInCell(c, pHeight, cellHeight, type); @@ -962,7 +953,7 @@ public class GridLayout extends ViewGroup { private class Axis { private static final int MIN_VALUE = -1000000; - private static final int UNVISITED = 0; + private static final int NEW = 0; private static final int PENDING = 1; private static final int COMPLETE = 2; @@ -975,8 +966,11 @@ public class GridLayout extends ViewGroup { PackedMap<Group, Bounds> groupBounds; public boolean groupBoundsValid = false; - PackedMap<Interval, MutableInt> spanSizes; - public boolean spanSizesValid = false; + PackedMap<Interval, MutableInt> forwardLinks; + public boolean forwardLinksValid = false; + + PackedMap<Interval, MutableInt> backwardLinks; + public boolean backwardLinksValid = false; public int[] leadingMargins; public boolean leadingMarginsValid = false; @@ -987,14 +981,14 @@ public class GridLayout extends ViewGroup { public Arc[] arcs; public boolean arcsValid = false; - public int[] minima; - public boolean minimaValid = false; - - public float[] weights; public int[] locations; + public boolean locationsValid = false; private boolean mOrderPreserved = DEFAULT_ORDER_PRESERVED; + private MutableInt parentMin = new MutableInt(0); + private MutableInt parentMax = new MutableInt(-MAX_SIZE); + private Axis(boolean horizontal) { this.horizontal = horizontal; } @@ -1036,22 +1030,19 @@ public class GridLayout extends ViewGroup { } private PackedMap<Group, Bounds> createGroupBounds() { - int N = getChildCount(); - Group[] groups = new Group[N]; - Arrays.fill(groups, Group.GONE); - Bounds[] bounds = new Bounds[N]; - Arrays.fill(bounds, Bounds.GONE); - for (int i = 0; i < N; i++) { + Assoc<Group, Bounds> assoc = Assoc.of(Group.class, Bounds.class); + for (int i = 0, N = getChildCount(); i < N; i++) { View c = getChildAt(i); - if (isGone(c)) continue; - LayoutParams lp = getLayoutParams(c); - Group group = horizontal ? lp.columnGroup : lp.rowGroup; - - groups[i] = group; - bounds[i] = group.alignment.getBounds(); + if (isGone(c)) { + assoc.put(Group.GONE, Bounds.GONE); + } else { + LayoutParams lp = getLayoutParams(c); + Group group = horizontal ? lp.columnGroup : lp.rowGroup; + Bounds bounds = group.alignment.getBounds(); + assoc.put(group, bounds); + } } - - return new PackedMap<Group, Bounds>(groups, bounds); + return assoc.pack(); } private void computeGroupBounds() { @@ -1064,13 +1055,7 @@ public class GridLayout extends ViewGroup { if (isGone(c)) continue; LayoutParams lp = getLayoutParams(c); Group g = horizontal ? lp.columnGroup : lp.rowGroup; - - Bounds bounds = groupBounds.getValue(i); - - int size = getMeasurementIncludingMargin(c, horizontal, PRF); - // todo test this works correctly when the returned value is UNDEFINED - int before = g.alignment.getAlignmentValue(c, size, PRF); - bounds.include(before, size - before); + groupBounds.getValue(i).include(c, g, GridLayout.this, this, lp); } } @@ -1086,80 +1071,91 @@ public class GridLayout extends ViewGroup { } // Add values computed by alignment - taking the max of all alignments in each span - private PackedMap<Interval, MutableInt> createSpanSizes() { - PackedMap<Group, Bounds> groupBounds = getGroupBounds(); - int N = groupBounds.keys.length; - Interval[] spans = new Interval[N]; - MutableInt[] values = new MutableInt[N]; - for (int i = 0; i < N; i++) { - Interval key = groupBounds.keys[i].span; - - spans[i] = key; - values[i] = new MutableInt(); + private PackedMap<Interval, MutableInt> createLinks(boolean min) { + Assoc<Interval, MutableInt> result = Assoc.of(Interval.class, MutableInt.class); + Group[] keys = getGroupBounds().keys; + for (int i = 0, N = keys.length; i < N; i++) { + Interval span = min ? keys[i].span : keys[i].span.inverse(); + result.put(span, new MutableInt()); } - return new PackedMap<Interval, MutableInt>(spans, values); + return result.pack(); } - private void computeSpanSizes() { - MutableInt[] spans = spanSizes.values; + private void computeLinks(PackedMap<Interval, MutableInt> links, boolean min) { + MutableInt[] spans = links.values; for (int i = 0; i < spans.length; i++) { spans[i].reset(); } - Bounds[] bounds = getGroupBounds().values; // use getter to trigger a re-evaluation + // use getter to trigger a re-evaluation + Bounds[] bounds = getGroupBounds().values; for (int i = 0; i < bounds.length; i++) { - int value = bounds[i].size(); - - MutableInt valueHolder = spanSizes.getValue(i); + int size = bounds[i].size(min); + int value = min ? size : -size; + MutableInt valueHolder = links.getValue(i); valueHolder.value = max(valueHolder.value, value); } } - private PackedMap<Interval, MutableInt> getSpanSizes() { - if (spanSizes == null) { - spanSizes = createSpanSizes(); + private PackedMap<Interval, MutableInt> getForwardLinks() { + if (forwardLinks == null) { + forwardLinks = createLinks(true); } - if (!spanSizesValid) { - computeSpanSizes(); - spanSizesValid = true; + if (!forwardLinksValid) { + computeLinks(forwardLinks, true); + forwardLinksValid = true; } - return spanSizes; + return forwardLinks; } - private void include(List<Arc> arcs, Interval key, MutableInt size) { - // this bit below should really be computed outside here - - // its just to stop default (col>0) constraints obliterating valid entries - for (Arc arc : arcs) { - Interval span = arc.span; - if (span.equals(key)) { - return; - } + private PackedMap<Interval, MutableInt> getBackwardLinks() { + if (backwardLinks == null) { + backwardLinks = createLinks(false); } - arcs.add(new Arc(key, size)); + if (!backwardLinksValid) { + computeLinks(backwardLinks, false); + backwardLinksValid = true; + } + return backwardLinks; } - private void include2(List<Arc> arcs, Interval span, MutableInt min, MutableInt max, - boolean both) { - include(arcs, span, min); - if (both) { - // todo -// include(arcs, span.inverse(), max.neg()); + private void include(List<Arc> arcs, Interval key, MutableInt size, + boolean ignoreIfAlreadyPresent) { + /* + Remove self referential links. + These appear: + . as parental constraints when GridLayout has no children + . when components have been marked as GONE + */ + if (key.size() == 0) { + return; } + // this bit below should really be computed outside here - + // its just to stop default (row/col > 0) constraints obliterating valid entries + if (ignoreIfAlreadyPresent) { + for (Arc arc : arcs) { + Interval span = arc.span; + if (span.equals(key)) { + return; + } + } + } + arcs.add(new Arc(key, size)); } - private void include2(List<Arc> arcs, Interval span, int min, int max, boolean both) { - include2(arcs, span, new MutableInt(min), new MutableInt(max), both); + private void include(List<Arc> arcs, Interval key, MutableInt size) { + include(arcs, key, size, true); } // Group arcs by their first vertex, returning an array of arrays. // This is linear in the number of arcs. private Arc[][] groupArcsByFirstVertex(Arc[] arcs) { - int N = getCount() + 1;// the number of vertices + int N = getCount() + 1; // the number of vertices Arc[][] result = new Arc[N][]; int[] sizes = new int[N]; for (Arc arc : arcs) { sizes[arc.span.min]++; - } + } for (int i = 0; i < sizes.length; i++) { result[i] = new Arc[sizes[i]]; } @@ -1173,38 +1169,46 @@ public class GridLayout extends ViewGroup { return result; } - private Arc[] topologicalSort(final Arc[] arcs, int start) { - // todo ensure the <start> vertex is added in edge cases - final List<Arc> result = new ArrayList<Arc>(); - new Object() { - Arc[][] arcsByFirstVertex = groupArcsByFirstVertex(arcs); + private Arc[] topologicalSort(final Arc[] arcs) { + return new Object() { + Arc[] result = new Arc[arcs.length]; + int cursor = result.length - 1; + Arc[][] arcsByVertex = groupArcsByFirstVertex(arcs); int[] visited = new int[getCount() + 1]; - boolean completesCycle(int loc) { - int state = visited[loc]; - if (state == UNVISITED) { - visited[loc] = PENDING; - for (Arc arc : arcsByFirstVertex[loc]) { - Interval span = arc.span; - // the recursive call - if (completesCycle(span.max)) { - // which arcs get set here is dependent on the order - // in which we explore nodes - arc.completesCycle = true; + void walk(int loc) { + switch (visited[loc]) { + case NEW: { + visited[loc] = PENDING; + for (Arc arc : arcsByVertex[loc]) { + walk(arc.span.max); + result[cursor--] = arc; } - result.add(arc); + visited[loc] = COMPLETE; + break; + } + case PENDING: { + assert false; + break; + } + case COMPLETE: { + break; } - visited[loc] = COMPLETE; - } else if (state == PENDING) { - return true; - } else if (state == COMPLETE) { } - return false; } - }.completesCycle(start); - Collections.reverse(result); - assert arcs.length == result.size(); - return result.toArray(new Arc[result.size()]); + + Arc[] sort() { + for (int loc = 0, N = arcsByVertex.length; loc < N; loc++) { + walk(loc); + } + assert cursor == -1; + return result; + } + }.sort(); + } + + private Arc[] topologicalSort(List<Arc> arcs) { + return topologicalSort(arcs.toArray(new Arc[arcs.size()])); } private boolean[] findUsed(Collection<Arc> arcs) { @@ -1254,43 +1258,64 @@ public class GridLayout extends ViewGroup { return result; } + private void addComponentSizes(List<Arc> result, PackedMap<Interval, MutableInt> links) { + for (int i = 0; i < links.keys.length; i++) { + Interval key = links.keys[i]; + include(result, key, links.values[i], false); + } + } + private Arc[] createArcs() { - List<Arc> result = new ArrayList<Arc>(); + List<Arc> mins = new ArrayList<Arc>(); + List<Arc> maxs = new ArrayList<Arc>(); - // Add all the preferred elements that were not defined by the user. - PackedMap<Interval, MutableInt> spanSizes = getSpanSizes(); - for (int i = 0; i < spanSizes.keys.length; i++) { - Interval key = spanSizes.keys[i]; - if (key == Interval.GONE) continue; - MutableInt value = spanSizes.values[i]; - // todo remove value duplicate - include2(result, key, value, value, accommodateBothMinAndMax); - } + // Add the minimum values from the components. + addComponentSizes(mins, getForwardLinks()); + // Add the maximum values from the components. + addComponentSizes(maxs, getBackwardLinks()); // Find redundant rows/cols and glue them together with 0-length arcs to link the tree - boolean[] used = findUsed(result); + boolean[] used = findUsed(mins); for (int i = 0; i < getCount(); i++) { if (!used[i]) { Interval span = new Interval(i, i + 1); - include(result, span, new MutableInt(0)); - include(result, span.inverse(), new MutableInt(0)); + include(mins, span, new MutableInt(0)); + include(maxs, span.inverse(), new MutableInt(0)); } } + // Add ordering constraints to prevent row/col sizes from going negative if (mOrderPreserved) { - // Add preferred gaps + // Add a constraint for every row/col for (int i = 0; i < getCount(); i++) { if (used[i]) { - include2(result, new Interval(i, i + 1), 0, 0, false); + include(mins, new Interval(i, i + 1), new MutableInt(0)); } } } else { + // Add a constraint for each row/col that separates opposing component edges for (Interval gap : getSpacers()) { - include2(result, gap, 0, 0, false); + include(mins, gap, new MutableInt(0)); } } - Arc[] arcs = result.toArray(new Arc[result.size()]); - return topologicalSort(arcs, 0); + + // Add the container constraints. Use the version of include that allows + // duplicate entries in case a child spans the entire grid. + int N = getCount(); + include(mins, new Interval(0, N), parentMin, false); + include(maxs, new Interval(N, 0), parentMax, false); + + // Sort + Arc[] sMins = topologicalSort(mins); + Arc[] sMaxs = topologicalSort(maxs); + + return append(sMins, sMaxs); + } + + private void computeArcs() { + // getting the links validates the values that are shared by the arc list + getForwardLinks(); + getBackwardLinks(); } public Arc[] getArcs() { @@ -1298,13 +1323,16 @@ public class GridLayout extends ViewGroup { arcs = createArcs(); } if (!arcsValid) { - getSpanSizes(); + computeArcs(); arcsValid = true; } return arcs; } private boolean relax(int[] locations, Arc entry) { + if (!entry.valid) { + return false; + } Interval span = entry.span; int u = span.min; int v = span.max; @@ -1351,7 +1379,8 @@ public class GridLayout extends ViewGroup { typical layout problems complete after the first iteration and the algorithm completes in O(N) steps with very low constants. */ - private int[] solve(Arc[] arcs, int[] locations) { + private void solve(Arc[] arcs, int[] locations) { + String axis = horizontal ? "horizontal" : "vertical"; int N = getCount() + 1; // The number of vertices is the number of columns/rows + 1. boolean changed = false; @@ -1359,20 +1388,44 @@ public class GridLayout extends ViewGroup { for (int i = 0; i < N; i++) { changed = false; for (int j = 0, length = arcs.length; j < length; j++) { - changed = changed | relax(locations, arcs[j]); + changed |= relax(locations, arcs[j]); } if (!changed) { if (DEBUG) { - Log.d(TAG, "Iteration " + - " completed after " + (1 + i) + " steps out of " + N); + Log.d(TAG, axis + " iteration completed in " + (1 + i) + " steps of " + N); } - break; + return; } } - if (changed) { - Log.d(TAG, "*** Algorithm failed to terminate ***"); + + 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]); + } } - return locations; + 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; + } + Log.d(TAG, "Removing: " + arc); + 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) { @@ -1418,11 +1471,11 @@ public class GridLayout extends ViewGroup { for (int i = 0, N = getCount(); i < N; i++) { int margins = leadingMargins[i] + trailingMargins[i + 1]; delta += margins; - minima[i + 1] += delta; + locations[i + 1] += delta; } } - private int getLocationIncludingMargin(View view, boolean leading, int index) { + private int getLocationIncludingMargin(boolean leading, int index) { int location = locations[index]; int margin; if (mAlignmentMode != ALIGN_MARGINS) { @@ -1433,53 +1486,22 @@ public class GridLayout extends ViewGroup { return leading ? (location + margin) : (location - margin); } - private void computeMinima(int[] a) { - Arrays.fill(a, MIN_VALUE); - a[0] = 0; - solve(getArcs(), a); + private void computeLocations(int[] a) { + solve1(getArcs(), a); if (mAlignmentMode != ALIGN_MARGINS) { addMargins(); } } - private int[] getMinima() { - if (minima == null) { - int N = getCount() + 1; - minima = new int[N]; - } - if (!minimaValid) { - computeMinima(minima); - minimaValid = true; - } - return minima; - } - - private void computeWeights() { - for (int i = 0, N = getChildCount(); i < N; i++) { - View c = getChildAt(i); - if (isGone(c)) continue; - LayoutParams lp = getLayoutParams(c); - Group g = horizontal ? lp.columnGroup : lp.rowGroup; - Interval span = g.span; - int penultimateIndex = span.max - 1; - weights[penultimateIndex] += horizontal ? lp.columnWeight : lp.rowWeight; - } - } - - private float[] getWeights() { - if (weights == null) { - int N = getCount(); - weights = new float[N]; - } - computeWeights(); - return weights; - } - private int[] getLocations() { if (locations == null) { int N = getCount() + 1; locations = new int[N]; } + if (!locationsValid) { + computeLocations(locations); + locationsValid = true; + } return locations; } @@ -1489,48 +1511,53 @@ public class GridLayout extends ViewGroup { return max2(locations, 0) - locations[0]; } - private int getMin() { - return size(getMinima()); + private void setParentConstraints(int min, int max) { + parentMin.value = min; + parentMax.value = -max; + locationsValid = false; } - private void layout(int targetSize) { - int[] mins = getMinima(); - - int totalDelta = max(0, targetSize - size(mins)); // confine to expansion - - float[] weights = getWeights(); - float totalWeight = sum(weights); + private int getMeasure(int min, int max) { + setParentConstraints(min, max); + return size(getLocations()); + } - if (totalWeight == 0f && weights.length > 0) { - weights[weights.length - 1] = 1; - totalWeight = 1; + private int getMeasure(int measureSpec) { + int mode = MeasureSpec.getMode(measureSpec); + int size = MeasureSpec.getSize(measureSpec); + switch (mode) { + case MeasureSpec.UNSPECIFIED: { + return getMeasure(0, MAX_SIZE); + } + case MeasureSpec.EXACTLY: { + return getMeasure(size, size); + } + case MeasureSpec.AT_MOST: { + return getMeasure(0, size); + } + default: { + assert false; + return 0; + } } + } - int[] locations = getLocations(); - int cumulativeDelta = 0; - - // note |weights| = |locations| - 1 - for (int i = 0; i < weights.length; i++) { - float weight = weights[i]; - int delta = (int) (totalDelta * weight / totalWeight); - cumulativeDelta += delta; - locations[i + 1] = mins[i + 1] + cumulativeDelta; - - totalDelta -= delta; - totalWeight -= weight; - } + private void layout(int size) { + setParentConstraints(size, size); + getLocations(); } private void invalidateStructure() { countValid = false; groupBounds = null; - spanSizes = null; + forwardLinks = null; + backwardLinks = null; + leadingMargins = null; trailingMargins = null; arcs = null; - minima = null; - weights = null; + locations = null; invalidateValues(); @@ -1538,11 +1565,14 @@ public class GridLayout extends ViewGroup { private void invalidateValues() { groupBoundsValid = false; - spanSizesValid = false; - arcsValid = false; + forwardLinksValid = false; + backwardLinksValid = false; + leadingMarginsValid = false; trailingMarginsValid = false; - minimaValid = false; + arcsValid = false; + + locationsValid = false; } } @@ -1592,16 +1622,16 @@ public class GridLayout extends ViewGroup { * <li>{@link #rowGroup}{@code .alignment} = {@link #BASELINE} </li> * <li>{@link #columnGroup}{@code .span} = {@code [0, 1]} </li> * <li>{@link #columnGroup}{@code .alignment} = {@link #LEFT} </li> - * <li>{@link #rowWeight} = {@code 0f} </li> - * <li>{@link #columnWeight} = {@code 0f} </li> + * <li>{@link #widthSpec} = {@link #FIXED} </li> + * <li>{@link #heightSpec} = {@link #FIXED} </li> * </ul> * * @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_rowWeight + * @attr ref android.R.styleable#GridLayout_Layout_layout_heightSpec * @attr ref android.R.styleable#GridLayout_Layout_layout_column * @attr ref android.R.styleable#GridLayout_Layout_layout_columnSpan - * @attr ref android.R.styleable#GridLayout_Layout_layout_columnWeight + * @attr ref android.R.styleable#GridLayout_Layout_layout_widthSpec * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity */ public static class LayoutParams extends MarginLayoutParams { @@ -1621,14 +1651,15 @@ public class GridLayout extends ViewGroup { new Group(DEFAULT_SPAN, DEFAULT_COLUMN_ALIGNMENT); private static final Group DEFAULT_ROW_GROUP = new Group(DEFAULT_SPAN, DEFAULT_ROW_ALIGNMENT); - private static final int DEFAULT_WEIGHT_0 = 0; - private static final int DEFAULT_WEIGHT_1 = 1; + private static final Spec DEFAULT_SPEC = FIXED; + private static final int DEFAULT_SPEC_INDEX = 0; // Misc private static final Rect CONTAINER_BOUNDS = new Rect(0, 0, 2, 2); private static final Alignment[] COLUMN_ALIGNMENTS = { LEFT, CENTER, RIGHT }; private static final Alignment[] ROW_ALIGNMENTS = { TOP, CENTER, BOTTOM }; + private static final Spec[] SPECS = { FIXED, CAN_SHRINK, CAN_STRETCH }; // TypedArray indices @@ -1641,10 +1672,10 @@ public class GridLayout extends ViewGroup { private static final int COLUMN = styleable.GridLayout_Layout_layout_column; private static final int COLUMN_SPAN = styleable.GridLayout_Layout_layout_columnSpan; - private static final int COLUMN_WEIGHT = styleable.GridLayout_Layout_layout_columnWeight; + private static final int WIDTH_SPEC = styleable.GridLayout_Layout_layout_widthSpec; private static final int ROW = styleable.GridLayout_Layout_layout_row; private static final int ROW_SPAN = styleable.GridLayout_Layout_layout_rowSpan; - private static final int ROW_WEIGHT = styleable.GridLayout_Layout_layout_rowWeight; + private static final int HEIGHT_SPEC = styleable.GridLayout_Layout_layout_heightSpec; private static final int GRAVITY = styleable.GridLayout_Layout_layout_gravity; // Instance variables @@ -1660,28 +1691,29 @@ public class GridLayout extends ViewGroup { */ public Group columnGroup; /** - * The proportional space that should be taken by the associated row group + * The proportional space that should be taken by the associated column group * during excess space distribution. */ - public float rowWeight; + public Spec widthSpec; /** - * The proportional space that should be taken by the associated column group + * The proportional space that should be taken by the associated row group * during excess space distribution. */ - public float columnWeight; + public Spec heightSpec; // Constructors private LayoutParams( int width, int height, int left, int top, int right, int bottom, - Group rowGroup, Group columnGroup, float rowWeight, float columnWeight) { + Group rowGroup, Group columnGroup, + Spec widthSpec, Spec heightSpec) { super(width, height); setMargins(left, top, right, bottom); this.rowGroup = rowGroup; this.columnGroup = columnGroup; - this.rowWeight = rowWeight; - this.columnWeight = columnWeight; + this.heightSpec = heightSpec; + this.widthSpec = widthSpec; } /** @@ -1695,7 +1727,7 @@ public class GridLayout extends ViewGroup { public LayoutParams(Group rowGroup, Group columnGroup) { this(DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, - rowGroup, columnGroup, DEFAULT_WEIGHT_0, DEFAULT_WEIGHT_0); + rowGroup, columnGroup, DEFAULT_SPEC, DEFAULT_SPEC); } /** @@ -1728,8 +1760,8 @@ public class GridLayout extends ViewGroup { super(that); this.columnGroup = that.columnGroup; this.rowGroup = that.rowGroup; - this.columnWeight = that.columnWeight; - this.rowWeight = that.rowWeight; + this.widthSpec = that.widthSpec; + this.heightSpec = that.heightSpec; } // AttributeSet constructors @@ -1813,11 +1845,6 @@ public class GridLayout extends ViewGroup { !definesVertical(gravity), defaultAlignment); } - private int getDefaultWeight(int size) { - //return (size == MATCH_PARENT) ? DEFAULT_WEIGHT_1 : DEFAULT_WEIGHT_0; - return DEFAULT_WEIGHT_0; - } - private void init(Context context, AttributeSet attrs, int defaultGravity) { TypedArray a = context.obtainStyledAttributes(attrs, styleable.GridLayout_Layout); try { @@ -1827,13 +1854,13 @@ public class GridLayout extends ViewGroup { int columnSpan = a.getInt(COLUMN_SPAN, DEFAULT_SPAN_SIZE); Interval hSpan = new Interval(column, column + columnSpan); this.columnGroup = new Group(hSpan, getColumnAlignment(gravity, width)); - this.columnWeight = a.getFloat(COLUMN_WEIGHT, getDefaultWeight(width)); + this.widthSpec = SPECS[a.getInt(WIDTH_SPEC, DEFAULT_SPEC_INDEX)]; int row = a.getInt(ROW, DEFAULT_ROW); int rowSpan = a.getInt(ROW_SPAN, DEFAULT_SPAN_SIZE); Interval vSpan = new Interval(row, row + rowSpan); this.rowGroup = new Group(vSpan, getRowAlignment(gravity, height)); - this.rowWeight = a.getFloat(ROW_WEIGHT, getDefaultWeight(height)); + this.heightSpec = SPECS[a.getInt(HEIGHT_SPEC, DEFAULT_SPEC_INDEX)]; } finally { a.recycle(); } @@ -1874,7 +1901,7 @@ public class GridLayout extends ViewGroup { private static class Arc { public final Interval span; public final MutableInt value; - public boolean completesCycle; + public boolean valid = true; public Arc(Interval span, MutableInt value) { this.span = span; @@ -1883,7 +1910,7 @@ public class GridLayout extends ViewGroup { @Override public String toString() { - return span + " " + (completesCycle ? "+>" : "->") + " " + value; + return span + " " + (!valid ? "+>" : "->") + " " + value; } } @@ -1903,6 +1930,41 @@ public class GridLayout extends ViewGroup { private void reset() { value = Integer.MIN_VALUE; } + + @Override + public String toString() { + return Integer.toString(value); + } + } + + private static class Assoc<K, V> extends ArrayList<Pair<K, V>> { + private final Class<K> keyType; + private final Class<V> valueType; + + private Assoc(Class<K> keyType, Class<V> valueType) { + this.keyType = keyType; + this.valueType = valueType; + } + + private static <K, V> Assoc<K, V> of(Class<K> keyType, Class<V> valueType) { + return new Assoc<K, V>(keyType, valueType); + } + + public void put(K key, V value) { + add(Pair.create(key, value)); + } + + @SuppressWarnings(value = "unchecked") + public PackedMap<K, V> pack() { + int N = size(); + K[] keys = (K[]) Array.newInstance(keyType, N); + V[] values = (V[]) Array.newInstance(valueType, N); + for (int i = 0; i < N; i++) { + keys[i] = get(i).first; + values[i] = get(i).second; + } + return new PackedMap<K, V>(keys, values); + } } /* @@ -1989,6 +2051,7 @@ public class GridLayout extends ViewGroup { public int before; public int after; + public boolean canStretch; private Bounds() { reset(); @@ -1997,6 +2060,7 @@ public class GridLayout extends ViewGroup { protected void reset() { before = Integer.MIN_VALUE; after = Integer.MIN_VALUE; + canStretch = false; } protected void include(int before, int after) { @@ -2004,12 +2068,26 @@ public class GridLayout extends ViewGroup { this.after = max(this.after, after); } - protected int size() { + protected int size(boolean min) { + if (!min && canStretch) { + return MAX_SIZE; + } return before + after; } - protected int getOffset(View c, Alignment alignment, int type, int size) { - return before - alignment.getAlignmentValue(c, size, type); + protected int getOffset(View c, Alignment alignment, int size) { + return before - alignment.getAlignmentValue(c, size); + } + + protected void include(View c, Group g, GridLayout gridLayout, Axis axis, LayoutParams lp) { + Spec spec = axis.horizontal ? lp.widthSpec : lp.heightSpec; + if (spec == CAN_STRETCH) { + canStretch = true; + } + int size = gridLayout.getMeasurementIncludingMargin(c, axis.horizontal); + // todo test this works correctly when the returned value is UNDEFINED + int before = g.alignment.getAlignmentValue(c, size); + include(before, size - before); } @Override @@ -2032,7 +2110,7 @@ public class GridLayout extends ViewGroup { * Intervals are often written as {@code [min, max]} and represent the set of values * {@code x} such that {@code min <= x < max}. */ - /* package */ static class Interval { + static class Interval { private static final Interval GONE = new Interval(UNDEFINED, UNDEFINED); /** @@ -2129,7 +2207,7 @@ public class GridLayout extends ViewGroup { * See {@link GridLayout} for a description of the conventions used by GridLayout * for grid indices. */ - /* package */ final Interval span; + final Interval span; /** * Specifies how cells should be aligned in this group. * For row groups, this specifies the vertical alignment. @@ -2147,7 +2225,7 @@ public class GridLayout extends ViewGroup { * @param span the span * @param alignment the alignment */ - /* package */ Group(Interval span, Alignment alignment) { + Group(Interval span, Alignment alignment) { this.span = span; this.alignment = alignment; } @@ -2248,17 +2326,16 @@ public class GridLayout extends ViewGroup { * so that the locations defined by the alignment values * are the same for all of the views in a group. * <p> - */ public static abstract class Alignment { private static final Alignment GONE = new Alignment() { - public int getAlignmentValue(View view, int viewSize, int measurementType) { + public int getAlignmentValue(View view, int viewSize) { assert false; return 0; } }; - /*pp*/ Alignment() { + Alignment() { } /** @@ -2269,12 +2346,9 @@ public class GridLayout extends ViewGroup { * * @param view the view to which this alignment should be applied * @param viewSize the measured size of the view - * @param measurementType This parameter is currently unused as GridLayout only supports - * one type of measurement: {@link View#measure(int, int)}. - * * @return the alignment value */ - /*pp*/ abstract int getAlignmentValue(View view, int viewSize, int measurementType); + abstract int getAlignmentValue(View view, int viewSize); /** * Returns the size of the view specified by this alignment. @@ -2291,24 +2365,24 @@ public class GridLayout extends ViewGroup { * * @return the aligned size */ - /*pp*/ int getSizeInCell(View view, int viewSize, int cellSize, int measurementType) { + int getSizeInCell(View view, int viewSize, int cellSize, int measurementType) { return viewSize; } - /*pp*/ Bounds getBounds() { + Bounds getBounds() { return new Bounds(); } } private static final Alignment LEADING = new Alignment() { - public int getAlignmentValue(View view, int viewSize, int measurementType) { + public int getAlignmentValue(View view, int viewSize) { return 0; } }; private static final Alignment TRAILING = new Alignment() { - public int getAlignmentValue(View view, int viewSize, int measurementType) { + public int getAlignmentValue(View view, int viewSize) { return viewSize; } }; @@ -2343,7 +2417,7 @@ public class GridLayout extends ViewGroup { * LayoutParams#columnGroup columnGroups}. */ public static final Alignment CENTER = new Alignment() { - public int getAlignmentValue(View view, int viewSize, int measurementType) { + public int getAlignmentValue(View view, int viewSize) { return viewSize >> 1; } }; @@ -2356,7 +2430,7 @@ public class GridLayout extends ViewGroup { * @see View#getBaseline() */ public static final Alignment BASELINE = new Alignment() { - public int getAlignmentValue(View view, int viewSize, int measurementType) { + public int getAlignmentValue(View view, int viewSize) { if (view == null) { return UNDEFINED; } @@ -2378,7 +2452,7 @@ public class GridLayout extends ViewGroup { @Override protected void reset() { super.reset(); - size = 0; + size = Integer.MIN_VALUE; } @Override @@ -2388,13 +2462,13 @@ public class GridLayout extends ViewGroup { } @Override - protected int size() { - return max(super.size(), size); + protected int size(boolean min) { + return max(super.size(min), size); } @Override - protected int getOffset(View c, Alignment alignment, int type, int size) { - return max(0, super.getOffset(c, alignment, type, size)); + protected int getOffset(View c, Alignment alignment, int size) { + return max(0, super.getOffset(c, alignment, size)); } }; } @@ -2406,7 +2480,7 @@ public class GridLayout extends ViewGroup { * {@link LayoutParams#columnGroup columnGroups}. */ public static final Alignment FILL = new Alignment() { - public int getAlignmentValue(View view, int viewSize, int measurementType) { + public int getAlignmentValue(View view, int viewSize) { return UNDEFINED; } @@ -2415,4 +2489,41 @@ public class GridLayout extends ViewGroup { return cellSize; } }; + + /** + * Spec's tell GridLayout how to derive minimum and maximum size values for a + * component. Specifications are made with respect to a child's 'measured size'. + * A child's measured size is, in turn, controlled by its height and width + * layout parameters which either specify a size or, in the case of + * WRAP_CONTENT, defer to the computed size of the component. + */ + public static abstract class Spec { + } + + /** + * Indicates that a view requests precisely the size specified by its layout parameters. + * + * @see Spec + */ + public static final Spec FIXED = new Spec() { + }; + + /** + * Indicates that a view's size should lie between its minimum and the size specified by + * its layout parameters. + * + * @see Spec + */ + public static final Spec CAN_SHRINK = new Spec() { + }; + + /** + * Indicates that a view's size should be greater than or equal to the size specified by + * its layout parameters. + * + * @see Spec + */ + public static final Spec CAN_STRETCH = new Spec() { + }; + } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 939779fe36f4..6154b42942c1 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -10104,6 +10104,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener case TEXT_DIRECTION_ANY_RTL: resolvedTextDirection = getTextDirectionFromAnyRtl(mText); break; + case TEXT_DIRECTION_CHAR_COUNT: + resolvedTextDirection = getTextDirectionFromCharCount(mText); + break; case TEXT_DIRECTION_LTR: resolvedTextDirection = TEXT_DIRECTION_LTR; break; @@ -10137,6 +10140,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ private static int getTextDirectionFromFirstStrong(final CharSequence cs) { final int length = cs.length(); + if (length == 0) { + return TEXT_DIRECTION_UNDEFINED; + } for(int i = 0; i < length; i++) { final char c = cs.charAt(i); final byte dir = Character.getDirectionality(c); @@ -10159,6 +10165,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ private static int getTextDirectionFromAnyRtl(final CharSequence cs) { final int length = cs.length(); + if (length == 0) { + return TEXT_DIRECTION_UNDEFINED; + } boolean foundStrongLtr = false; boolean foundStrongRtl = false; for(int i = 0; i < length; i++) { @@ -10180,6 +10189,41 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** + * Get text direction following the "char count" heuristic. + * + * @param cs the CharSequence used to get the text direction. + * + * @return {@link #TEXT_DIRECTION_RTL} if direction it RTL, {@link #TEXT_DIRECTION_LTR} if + * direction it LTR or {@link #TEXT_DIRECTION_UNDEFINED} if direction cannot be found. + */ + private int getTextDirectionFromCharCount(CharSequence cs) { + final int length = cs.length(); + if (length == 0) { + return TEXT_DIRECTION_UNDEFINED; + } + int countLtr = 0; + int countRtl = 0; + for(int i = 0; i < length; i++) { + final char c = cs.charAt(i); + final byte dir = Character.getDirectionality(c); + if (isStrongLtrChar(dir)) { + countLtr++; + } else if (isStrongRtlChar(dir)) { + countRtl++; + } + } + final float percentLtr = ((float) countLtr) / (countLtr + countRtl); + if (percentLtr > DEFAULT_TEXT_DIRECTION_CHAR_COUNT_THRESHOLD) { + return TEXT_DIRECTION_LTR; + } + final float percentRtl = ((float) countRtl) / (countLtr + countRtl); + if (percentRtl > DEFAULT_TEXT_DIRECTION_CHAR_COUNT_THRESHOLD) { + return TEXT_DIRECTION_RTL; + } + return TEXT_DIRECTION_UNDEFINED; + } + + /** * Return true if the char direction is corresponding to a "strong RTL char" following the * Unicode Bidirectional Algorithm (UBA). */ diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java index 773be5bc2b21..572a1d761d65 100644 --- a/core/java/com/android/internal/net/VpnConfig.java +++ b/core/java/com/android/internal/net/VpnConfig.java @@ -23,6 +23,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; +import java.util.List; + /** * A simple container used to carry information in VpnBuilder, VpnDialogs, * and com.android.server.connectivity.Vpn. Internal use only. @@ -33,12 +35,6 @@ public class VpnConfig implements Parcelable { public static final String ACTION_VPN_REVOKED = "android.net.vpn.action.REVOKED"; - public static void enforceCallingPackage(String packageName) { - if (!"com.android.vpndialogs".equals(packageName)) { - throw new SecurityException("Unauthorized Caller"); - } - } - public static Intent getIntentForConfirmation() { Intent intent = new Intent(); intent.setClassName("com.android.vpndialogs", "com.android.vpndialogs.ConfirmDialog"); @@ -58,11 +54,12 @@ public class VpnConfig implements Parcelable { public String packageName; public String sessionName; public String interfaceName; - public String configureActivity; + public PendingIntent configureIntent; public int mtu = -1; public String addresses; public String routes; - public String dnsServers; + public List<String> dnsServers; + public List<String> searchDomains; public long startTime = -1; @Override @@ -75,11 +72,12 @@ public class VpnConfig implements Parcelable { out.writeString(packageName); out.writeString(sessionName); out.writeString(interfaceName); - out.writeString(configureActivity); + out.writeParcelable(configureIntent, flags); out.writeInt(mtu); out.writeString(addresses); out.writeString(routes); - out.writeString(dnsServers); + out.writeStringList(dnsServers); + out.writeStringList(searchDomains); out.writeLong(startTime); } @@ -91,11 +89,12 @@ public class VpnConfig implements Parcelable { config.packageName = in.readString(); config.sessionName = in.readString(); config.interfaceName = in.readString(); - config.configureActivity = in.readString(); + config.configureIntent = in.readParcelable(null); config.mtu = in.readInt(); config.addresses = in.readString(); config.routes = in.readString(); - config.dnsServers = in.readString(); + config.dnsServers = in.createStringArrayList(); + config.searchDomains = in.createStringArrayList(); config.startTime = in.readLong(); return config; } diff --git a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java index 322a8545d559..2fec9cd09f5b 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java +++ b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java @@ -26,6 +26,7 @@ import android.view.MenuItem; import android.view.SoundEffectConstants; import android.view.View; import android.view.View.MeasureSpec; +import android.view.ViewConfiguration; import android.view.ViewGroup; import android.widget.ImageButton; @@ -69,9 +70,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter { final Resources res = context.getResources(); if (!mReserveOverflowSet) { - // TODO Use the no-buttons specifier instead here - mReserveOverflow = res.getConfiguration() - .isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE); + mReserveOverflow = !ViewConfiguration.get(context).hasPermanentMenuKey(); } if (!mWidthLimitSet) { diff --git a/core/res/res/layout/keyguard_screen_password_landscape.xml b/core/res/res/layout/keyguard_screen_password_landscape.xml index 8ba08f68f17f..30df91b24450 100644 --- a/core/res/res/layout/keyguard_screen_password_landscape.xml +++ b/core/res/res/layout/keyguard_screen_password_landscape.xml @@ -156,7 +156,7 @@ /> <!-- Column 1 --> - <Space android:layout_columnWeight="1" android:layout_rowSpan="11" /> + <Space android:layout_widthSpec="canStretch" android:layout_rowSpan="11" /> <!-- Column 2 - password entry field and PIN keyboard --> <LinearLayout diff --git a/core/res/res/layout/keyguard_screen_tab_unlock_land.xml b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml index 5588adc88a81..c8597209e763 100644 --- a/core/res/res/layout/keyguard_screen_tab_unlock_land.xml +++ b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml @@ -169,7 +169,10 @@ </LinearLayout> <!-- Column 1 --> - <Space android:width="20dip" android:layout_columnWeight="1" android:layout_rowSpan="10" /> + <Space + android:width="20dip" + android:layout_heightSpec="canStretch" + android:layout_rowSpan="10" /> <!-- Column 2 --> <com.android.internal.widget.multiwaveview.MultiWaveView diff --git a/core/res/res/layout/keyguard_screen_unlock_landscape.xml b/core/res/res/layout/keyguard_screen_unlock_landscape.xml index d0538dd09b63..0070ed0ec87b 100644 --- a/core/res/res/layout/keyguard_screen_unlock_landscape.xml +++ b/core/res/res/layout/keyguard_screen_unlock_landscape.xml @@ -100,9 +100,11 @@ <!-- TODO: remove hard coded height since layout_rowWeight doesn't seem to be working --> <Space - android:layout_height="43dip" - android:layout_gravity="fill" - android:layout_rowWeight="1" android:layout_columnWeight="1" /> + android:layout_height="43dip" + android:layout_gravity="fill" + android:layout_heightSpec="canStretch" + android:layout_widthSpec="canStretch" + /> <TextView android:id="@+id/carrier" android:layout_gravity="right" diff --git a/core/res/res/layout/keyguard_screen_unlock_portrait.xml b/core/res/res/layout/keyguard_screen_unlock_portrait.xml index 774f830925f5..28c530291bcd 100644 --- a/core/res/res/layout/keyguard_screen_unlock_portrait.xml +++ b/core/res/res/layout/keyguard_screen_unlock_portrait.xml @@ -125,7 +125,7 @@ android:layout_marginBottom="4dip" android:layout_marginLeft="8dip" android:layout_gravity="center|bottom" - android:layout_rowWeight="1" + android:layout_heightSpec="canStretch" /> <TextView diff --git a/core/res/res/layout/search_view.xml b/core/res/res/layout/search_view.xml index 475aa5922f89..fee27ebe67c3 100644 --- a/core/res/res/layout/search_view.xml +++ b/core/res/res/layout/search_view.xml @@ -54,8 +54,8 @@ android:layout_height="wrap_content" android:layout_weight="1" android:layout_gravity="center_vertical" - android:layout_marginLeft="16dip" - android:layout_marginRight="16dip" + android:layout_marginLeft="8dip" + android:layout_marginRight="8dip" android:layout_marginTop="4dip" android:layout_marginBottom="4dip" android:orientation="horizontal"> @@ -87,7 +87,6 @@ android:layout_gravity="bottom" android:paddingLeft="8dip" android:paddingRight="6dip" - android:drawablePadding="2dip" android:singleLine="true" android:ellipsize="end" android:background="@null" diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index db33d1c4e19f..e9b845da4bf5 100755 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -1985,10 +1985,14 @@ it is LTR if it contains any strong LTR characters. If there are neither, the paragraph direction is the view’s resolved layout direction. --> <enum name="anyRtl" value="2" /> - <!-- The text direction is left to right. --> - <enum name="ltr" value="3" /> - <!-- The text direction is right to left. --> - <enum name="rtl" value="4" /> + <!-- The paragraph direction is the same as the one held by a 60% majority of the + characters. If there is no majority then the paragraph direction is the resolved + layout direction of the View. --> + <enum name="charCount" value="3" /> + <!-- The paragraph direction is left to right. --> + <enum name="ltr" value="4" /> + <!-- The paragraph direction is right to left. --> + <enum name="rtl" value="5" /> </attr> </declare-styleable> @@ -3324,11 +3328,6 @@ The default is one. See {@link android.widget.GridLayout.Group}. --> <attr name="layout_rowSpan" format="integer" min="1" /> - <!-- A number indicating the relative proportion of available space that - should be taken by this group of cells. - The default is zero. - See {@link android.widget.GridLayout.LayoutParams#columnWeight}. --> - <attr name="layout_rowWeight" format="float" /> <!-- The column boundary delimiting the left of the group of cells occupied by this view. --> <attr name="layout_column" /> @@ -3337,15 +3336,38 @@ The default is one. See {@link android.widget.GridLayout.Group}. --> <attr name="layout_columnSpan" format="integer" min="1" /> - <!-- A number indicating the relative proportion of available space that - should be taken by this group of cells. - The default is zero. - See {@link android.widget.GridLayout.LayoutParams#columnWeight}.--> - <attr name="layout_columnWeight" format="float" /> <!-- Gravity specifies how a component should be placed in its group of cells. The default is LEFT | BASELINE. See {@link android.widget.GridLayout.LayoutParams#setGravity(int)}. --> <attr name="layout_gravity" /> + <!-- A value specifying how much deficit or excess width this component can accomodate. + The default is FIXED. + See {@link android.widget.GridLayout.LayoutParams#widthSpec}.--> + <attr name="layout_widthSpec" > + <!-- If possible, width should be exactly as specified. + See {@link android.widget.GridLayout#FIXED}. --> + <enum name="fixed" value="0" /> + <!-- If possible, width should be less than or equal to the specified width. + See {@link android.widget.GridLayout#CAN_SHRINK}. --> + <enum name="canShrink" value="1" /> + <!-- If possible, width should be greater than or equal to the specified width. + See {@link android.widget.GridLayout#CAN_STRETCH}. --> + <enum name="canStretch" value="2" /> + </attr> + <!-- A value specifying how much deficit or excess height this component can accomodate. + The default is FIXED. + See {@link android.widget.GridLayout.LayoutParams#heightSpec}.--> + <attr name="layout_heightSpec" > + <!-- If possible, height should be exactly as specified. + See {@link android.widget.GridLayout#FIXED}. --> + <enum name="fixed" value="0" /> + <!-- If possible, height should be less than or equal to the specified height. + See {@link android.widget.GridLayout#CAN_SHRINK}. --> + <enum name="canShrink" value="1" /> + <!-- If possible, height should be greater than or equal to the specified height. + See {@link android.widget.GridLayout#CAN_STRETCH}. --> + <enum name="canStretch" value="2" /> + </attr> </declare-styleable> <declare-styleable name="FrameLayout_Layout"> <attr name="layout_gravity" /> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 827153e7243e..2c10b3dabe8f 100755 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -658,4 +658,8 @@ This is intended to allow packaging drivers or tools for installation on a PC. --> <string translatable="false" name="config_isoImagePath"></string> + <!-- Whether a software navigation bar should be shown. NOTE: in the future this may be + autodetected from the Configuration. --> + <bool name="config_showNavigationBar">false</bool> + </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index db6f98fa3400..945e0c483fad 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -1738,9 +1738,11 @@ <public type="attr" name="layout_row" /> <public type="attr" name="layout_rowSpan" /> - <public type="attr" name="layout_rowWeight" /> <public type="attr" name="layout_columnSpan" /> - <public type="attr" name="layout_columnWeight" /> + + <public type="attr" name="layout_widthSpec" /> + <public type="attr" name="layout_heightSpec" /> + <public type="attr" name="actionModeSelectAllDrawable" /> <public type="attr" name="isAuxiliary" /> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 88ed9c6bc586..c8b3b4fbfa81 100755 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -2073,6 +2073,18 @@ <!-- Do not translate. Regex used by AutoFill. --> <string name="autofill_fax_re">fax<!-- fr-FR -->|télécopie|telecopie<!-- ja-JP -->|ファックス<!-- ru -->|факс<!-- zh-CN -->|传真<!-- zh-TW -->|傳真</string> + <!-- Do not translate. Regex used by AutoFill. --> + <string name="autofill_country_code_re">country.*code|ccode|_cc</string> + + <!-- Do not translate. Regex used by AutoFill. --> + <string name="autofill_area_code_notext_re">^\($</string> + + <!-- Do not translate. Regex used by AutoFill. --> + <string name="autofill_phone_prefix_separator_re">^-$|^\)$</string> + + <!-- Do not translate. Regex used by AutoFill. --> + <string name="autofill_phone_suffix_separator_re">^-$</string> + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_readHistoryBookmarks">read Browser\'s history and bookmarks</string> @@ -2613,10 +2625,14 @@ <!-- USB_STORAGE_ERROR dialog ok button--> <string name="dlg_ok">OK</string> - <!-- USB_PREFERENCES: When the user connects the phone to a computer via USB, we show a notification asking if he wants to share files across. This is the title --> - <string name="usb_preferences_notification_title">USB connected</string> + <!-- USB_PREFERENCES: Notification for wehen the user connects the phone to a computer via USB in MTP mode. This is the title --> + <string name="usb_mtp_notification_title">Connected as a media device</string> + <!-- USB_PREFERENCES: Notification for wehen the user connects the phone to a computer via USB in PTP mode. This is the title --> + <string name="usb_ptp_notification_title">Connected as a camera</string> + <!-- USB_PREFERENCES: Notification for wehen the user connects the phone to a computer via USB in mass storage mode (for installer CD image). This is the title --> + <string name="usb_cd_installer_notification_title">Connected as an installer</string> <!-- See USB_PREFERENCES. This is the message. --> - <string name="usb_preferece_notification_message">Select to configure USB file transfer.</string> + <string name="usb_notification_message">Touch for other USB options</string> <!-- External media format dialog strings --> <!-- This is the label for the activity, and should never be visible to the user. --> @@ -2788,10 +2804,10 @@ <string name="l2tp_ipsec_psk_vpn_description">Pre-shared key based L2TP/IPSec VPN</string> <string name="l2tp_ipsec_crt_vpn_description">Certificate based L2TP/IPSec VPN</string> - <!-- Ticker text to show when VPN is active. --> - <string name="vpn_ticker"><xliff:g id="app" example="FooVPN client">%s</xliff:g> is activating VPN...</string> <!-- The title of the notification when VPN is active. --> - <string name="vpn_title">VPN is activated by <xliff:g id="app" example="FooVPN client">%s</xliff:g></string> + <string name="vpn_title">VPN is activated.</string> + <!-- The title of the notification when VPN is active with an application name. --> + <string name="vpn_title_long">VPN is activated by <xliff:g id="app" example="FooVPN client">%s</xliff:g></string> <!-- The text of the notification when VPN is active. --> <string name="vpn_text">Tap to manage the network.</string> <!-- The text of the notification when VPN is active with a session name. --> diff --git a/core/tests/coretests/src/android/widget/TextViewTest.java b/core/tests/coretests/src/android/widget/TextViewTest.java index a37f1a37e8dc..6db67c0de787 100644 --- a/core/tests/coretests/src/android/widget/TextViewTest.java +++ b/core/tests/coretests/src/android/widget/TextViewTest.java @@ -177,4 +177,38 @@ public class TextViewTest extends AndroidTestCase { tv.setTextDirection(View.TEXT_DIRECTION_RTL); assertEquals(View.TEXT_DIRECTION_RTL, tv.getResolvedTextDirection()); } + + @SmallTest + public void testCharCountHeuristic() { + LinearLayout ll = new LinearLayout(mContext); + ll.setLayoutDirection(View.LAYOUT_DIRECTION_RTL); + + TextView tv = new TextView(mContext); + ll.addView(tv); + + tv.setTextDirection(View.TEXT_DIRECTION_CHAR_COUNT); + tv.setText("this is a test"); + assertEquals(View.TEXT_DIRECTION_LTR, tv.getResolvedTextDirection()); + + // resetResolvedTextDirection is not part of the public API so simply use setTextDirection + tv.setTextDirection(View.TEXT_DIRECTION_LTR); + tv.setTextDirection(View.TEXT_DIRECTION_CHAR_COUNT); + tv.setText("\u05DD\u05DE"); // hebrew + assertEquals(View.TEXT_DIRECTION_RTL, tv.getResolvedTextDirection()); + + tv.setTextDirection(View.TEXT_DIRECTION_LTR); + tv.setTextDirection(View.TEXT_DIRECTION_CHAR_COUNT); + tv.setText("this is a test \u05DD\u05DE"); // latin more than 60% + hebrew + assertEquals(View.TEXT_DIRECTION_LTR, tv.getResolvedTextDirection()); + + tv.setTextDirection(View.TEXT_DIRECTION_LTR); + tv.setTextDirection(View.TEXT_DIRECTION_CHAR_COUNT); + tv.setText("t \u05DD\u05DE"); // latin + hebrew more than 60% + assertEquals(View.TEXT_DIRECTION_RTL, tv.getResolvedTextDirection()); + + tv.setTextDirection(View.TEXT_DIRECTION_LTR); + tv.setTextDirection(View.TEXT_DIRECTION_CHAR_COUNT); + tv.setText("ab \u05DD\u05DE"); // latin + hebrew at 50% each + assertEquals(View.TEXT_DIRECTION_RTL, tv.getResolvedTextDirection()); + } } diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java index 90a7ac22335b..1647ff3481a1 100644 --- a/graphics/java/android/graphics/SurfaceTexture.java +++ b/graphics/java/android/graphics/SurfaceTexture.java @@ -174,12 +174,15 @@ public class SurfaceTexture { * Retrieve the timestamp associated with the texture image set by the most recent call to * updateTexImage. * - * This timestamp is in nanoseconds, and is guaranteed to be monotonically increasing. The + * This timestamp is in nanoseconds, and is normally monotonically increasing. The timestamp + * should be unaffected by time-of-day adjustments, and for a camera should be strictly + * monotonic but for a MediaPlayer may be reset when the position is set. The * specific meaning and zero point of the timestamp depends on the source providing images to * the SurfaceTexture. Unless otherwise specified by the image source, timestamps cannot * generally be compared across SurfaceTexture instances, or across multiple program * invocations. It is mostly useful for determining time offsets between subsequent frames. */ + public long getTimestamp() { return nativeGetTimestamp(); } diff --git a/include/media/MediaPlayerInterface.h b/include/media/MediaPlayerInterface.h index 18e8a5f39254..4328d3cc6d74 100644 --- a/include/media/MediaPlayerInterface.h +++ b/include/media/MediaPlayerInterface.h @@ -103,6 +103,10 @@ public: virtual status_t initCheck() = 0; virtual bool hardwareOutput() = 0; + virtual status_t setUID(uid_t uid) { + return INVALID_OPERATION; + } + virtual status_t setDataSource( const char *url, const KeyedVector<String8, String8> *headers = NULL) = 0; diff --git a/include/media/stagefright/MediaSource.h b/include/media/stagefright/MediaSource.h index a31395ed9509..37dbcd8eae84 100644 --- a/include/media/stagefright/MediaSource.h +++ b/include/media/stagefright/MediaSource.h @@ -22,6 +22,7 @@ #include <media/stagefright/MediaErrors.h> #include <utils/RefBase.h> +#include <utils/Vector.h> namespace android { @@ -99,6 +100,15 @@ struct MediaSource : public RefBase { return ERROR_UNSUPPORTED; } + // The consumer of this media source requests that the given buffers + // are to be returned exclusively in response to read calls. + // This will be called after a successful start() and before the + // first read() call. + // Callee assumes ownership of the buffers if no error is returned. + virtual status_t setBuffers(const Vector<MediaBuffer *> &buffers) { + return ERROR_UNSUPPORTED; + } + protected: virtual ~MediaSource(); diff --git a/include/media/stagefright/MetaData.h b/include/media/stagefright/MetaData.h index 99b72ad86415..57f678c75512 100644 --- a/include/media/stagefright/MetaData.h +++ b/include/media/stagefright/MetaData.h @@ -121,6 +121,8 @@ enum { // To store the timed text format data kKeyTextFormatData = 'text', // raw data + + kKeyRequiresSecureBuffers = 'secu', // bool (int32_t) }; enum { diff --git a/include/media/stagefright/MetadataBufferType.h b/include/media/stagefright/MetadataBufferType.h new file mode 100644 index 000000000000..275c19f679d5 --- /dev/null +++ b/include/media/stagefright/MetadataBufferType.h @@ -0,0 +1,77 @@ +/* + * 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. + */ + +#ifndef METADATA_BUFFER_TYPE_H +#define METADATA_BUFFER_TYPE_H + +namespace android { +/* + * MetadataBufferType defines the type of the metadata buffers that + * can be passed to video encoder component for encoding, via Stagefright + * media recording framework. To see how to work with the metadata buffers + * in media recording framework, please consult HardwareAPI.h + * + * The creator of metadata buffers and video encoder share common knowledge + * on what is actually being stored in these metadata buffers, and + * how the information can be used by the video encoder component + * to locate the actual pixel data as the source input for video + * encoder, plus whatever other information that is necessary. Stagefright + * media recording framework does not need to know anything specific about the + * metadata buffers, except for receving each individual metadata buffer + * as the source input, making a copy of the metadata buffer, and passing the + * copy via OpenMAX API to the video encoder component. + * + * The creator of the metadata buffers must ensure that the first + * 4 bytes in every metadata buffer indicates its buffer type, + * and the rest of the metadata buffer contains the + * actual metadata information. When a video encoder component receives + * a metadata buffer, it uses the first 4 bytes in that buffer to find + * out the type of the metadata buffer, and takes action appropriate + * to that type of metadata buffers (for instance, locate the actual + * pixel data input and then encoding the input data to produce a + * compressed output buffer). + * + * The following shows the layout of a metadata buffer, + * where buffer type is a 4-byte field of MetadataBufferType, + * and the payload is the metadata information. + * + * -------------------------------------------------------------- + * | buffer type | payload | + * -------------------------------------------------------------- + * + */ +typedef enum { + + /* + * kMetadataBufferTypeCameraSource is used to indicate that + * the source of the metadata buffer is the camera component. + */ + kMetadataBufferTypeCameraSource = 0, + + /* + * kMetadataBufferTypeGrallocSource is used to indicate that + * the payload of the metadata buffers can be interpreted as + * a buffer_handle_t. + */ + kMetadataBufferTypeGrallocSource = 1, + + // Add more here... + +} MetadataBufferType; + +} // namespace android + +#endif // METADATA_BUFFER_TYPE_H diff --git a/include/media/stagefright/OMXCodec.h b/include/media/stagefright/OMXCodec.h index 92331a16f96a..7f3c497ecb32 100644 --- a/include/media/stagefright/OMXCodec.h +++ b/include/media/stagefright/OMXCodec.h @@ -53,6 +53,9 @@ struct OMXCodec : public MediaSource, // Enable GRALLOC_USAGE_PROTECTED for output buffers from native window kEnableGrallocUsageProtected = 128, + + // Secure decoding mode + kUseSecureInputBuffers = 256, }; static sp<MediaSource> Create( const sp<IOMX> &omx, @@ -164,6 +167,10 @@ private: bool mOMXLivesLocally; IOMX::node_id mNode; uint32_t mQuirks; + + // Flags specified in the creation of the codec. + uint32_t mFlags; + bool mIsEncoder; char *mMIME; char *mComponentName; @@ -205,15 +212,12 @@ private: List<size_t> mFilledBuffers; Condition mBufferFilled; - bool mIsMetaDataStoredInVideoBuffers; - bool mOnlySubmitOneBufferAtOneTime; - bool mEnableGrallocUsageProtected; - // Used to record the decoding time for an output picture from // a video encoder. List<int64_t> mDecodingTimeList; - OMXCodec(const sp<IOMX> &omx, IOMX::node_id node, uint32_t quirks, + OMXCodec(const sp<IOMX> &omx, IOMX::node_id node, + uint32_t quirks, uint32_t flags, bool isEncoder, const char *mime, const char *componentName, const sp<MediaSource> &source, const sp<ANativeWindow> &nativeWindow); @@ -287,6 +291,10 @@ private: void drainInputBuffers(); void fillOutputBuffers(); + bool drainAnyInputBuffer(); + BufferInfo *findInputBufferByDataPointer(void *ptr); + BufferInfo *findEmptyInputBuffer(); + // Returns true iff a flush was initiated and a completion event is // upcoming, false otherwise (A flush was not necessary as we own all // the buffers on that port). @@ -313,7 +321,7 @@ private: void dumpPortStatus(OMX_U32 portIndex); - status_t configureCodec(const sp<MetaData> &meta, uint32_t flags); + status_t configureCodec(const sp<MetaData> &meta); static uint32_t getComponentQuirks( const char *componentName, bool isEncoder); diff --git a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java index ffc3346da993..29dec6373b19 100755 --- a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java +++ b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java @@ -205,7 +205,7 @@ public class GpsNetInitiatedHandler { mNiNotification.defaults &= ~Notification.DEFAULT_SOUND; } - mNiNotification.flags = Notification.FLAG_ONGOING_EVENT; + mNiNotification.flags = Notification.FLAG_ONGOING_EVENT | Notification.FLAG_AUTO_CANCEL; mNiNotification.tickerText = getNotifTicker(notif, mContext); // if not to popup dialog immediately, pending intent will open the dialog diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index 33312d195656..482b437dd3dc 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -611,15 +611,11 @@ public class MediaPlayer * needed. Not calling this method when playing back a video will * result in only the audio track being played. * - * @param sh the SurfaceHolder to use for video display - */ - /* - * This portion of comment has a non-Javadoc prefix so as not to refer to a - * hidden method. When unhidden, merge it with the previous javadoc comment. - * * Either a surface or surface texture must be set if a display or video sink * is needed. Not calling this method or {@link #setTexture(SurfaceTexture)} * when playing back a video will result in only the audio track being played. + * + * @param sh the SurfaceHolder to use for video display */ public void setDisplay(SurfaceHolder sh) { mSurfaceHolder = sh; @@ -648,7 +644,8 @@ public class MediaPlayer * SurfaceTexture set as the video sink have an unspecified zero point, * and cannot be directly compared between different media sources or different * instances of the same media source, or across multiple runs of the same - * program. + * program. The timestamp is normally monotonically increasing and unaffected + * by time-of-day adjustments, but is reset when the position is set. */ public void setTexture(SurfaceTexture st) { ParcelSurfaceTexture pst = null; diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp index a77dff1615d4..1e7c9693bc1a 100644 --- a/media/libmediaplayerservice/MediaPlayerService.cpp +++ b/media/libmediaplayerservice/MediaPlayerService.cpp @@ -250,7 +250,11 @@ sp<IMediaPlayer> MediaPlayerService::create( const KeyedVector<String8, String8> *headers, int audioSessionId) { int32_t connId = android_atomic_inc(&mNextConnId); - sp<Client> c = new Client(this, pid, connId, client, audioSessionId); + + sp<Client> c = new Client( + this, pid, connId, client, audioSessionId, + IPCThreadState::self()->getCallingUid()); + LOGV("Create new client(%d) from pid %d, url=%s, connId=%d, audioSessionId=%d", connId, pid, url, connId, audioSessionId); if (NO_ERROR != c->setDataSource(url, headers)) @@ -268,7 +272,11 @@ sp<IMediaPlayer> MediaPlayerService::create(pid_t pid, const sp<IMediaPlayerClie int fd, int64_t offset, int64_t length, int audioSessionId) { int32_t connId = android_atomic_inc(&mNextConnId); - sp<Client> c = new Client(this, pid, connId, client, audioSessionId); + + sp<Client> c = new Client( + this, pid, connId, client, audioSessionId, + IPCThreadState::self()->getCallingUid()); + LOGV("Create new client(%d) from pid %d, fd=%d, offset=%lld, length=%lld, audioSessionId=%d", connId, pid, fd, offset, length, audioSessionId); if (NO_ERROR != c->setDataSource(fd, offset, length)) { @@ -286,7 +294,10 @@ sp<IMediaPlayer> MediaPlayerService::create( pid_t pid, const sp<IMediaPlayerClient> &client, const sp<IStreamSource> &source, int audioSessionId) { int32_t connId = android_atomic_inc(&mNextConnId); - sp<Client> c = new Client(this, pid, connId, client, audioSessionId); + + sp<Client> c = new Client( + this, pid, connId, client, audioSessionId, + IPCThreadState::self()->getCallingUid()); LOGV("Create new client(%d) from pid %d, audioSessionId=%d", connId, pid, audioSessionId); @@ -496,8 +507,10 @@ void MediaPlayerService::removeClient(wp<Client> client) mClients.remove(client); } -MediaPlayerService::Client::Client(const sp<MediaPlayerService>& service, pid_t pid, - int32_t connId, const sp<IMediaPlayerClient>& client, int audioSessionId) +MediaPlayerService::Client::Client( + const sp<MediaPlayerService>& service, pid_t pid, + int32_t connId, const sp<IMediaPlayerClient>& client, + int audioSessionId, uid_t uid) { LOGV("Client(%d) constructor", connId); mPid = pid; @@ -507,6 +520,7 @@ MediaPlayerService::Client::Client(const sp<MediaPlayerService>& service, pid_t mLoop = false; mStatus = NO_INIT; mAudioSessionId = audioSessionId; + mUID = uid; #if CALLBACK_ANTAGONIZER LOGD("create Antagonizer"); @@ -671,6 +685,9 @@ sp<MediaPlayerBase> MediaPlayerService::Client::createPlayer(player_type playerT if (p == NULL) { p = android::createPlayer(playerType, this, notify); } + + p->setUID(mUID); + return p; } diff --git a/media/libmediaplayerservice/MediaPlayerService.h b/media/libmediaplayerservice/MediaPlayerService.h index 8bab471fac6b..e32b92a659ca 100644 --- a/media/libmediaplayerservice/MediaPlayerService.h +++ b/media/libmediaplayerservice/MediaPlayerService.h @@ -306,7 +306,8 @@ private: pid_t pid, int32_t connId, const sp<IMediaPlayerClient>& client, - int audioSessionId); + int audioSessionId, + uid_t uid); Client(); virtual ~Client(); @@ -336,6 +337,7 @@ private: bool mLoop; int32_t mConnId; int mAudioSessionId; + uid_t mUID; // Metadata filters. media::Metadata::Filter mMetadataAllow; // protected by mLock diff --git a/media/libmediaplayerservice/StagefrightPlayer.cpp b/media/libmediaplayerservice/StagefrightPlayer.cpp index 870e290ee09e..40e055cc5aca 100644 --- a/media/libmediaplayerservice/StagefrightPlayer.cpp +++ b/media/libmediaplayerservice/StagefrightPlayer.cpp @@ -47,6 +47,12 @@ status_t StagefrightPlayer::initCheck() { return OK; } +status_t StagefrightPlayer::setUID(uid_t uid) { + mPlayer->setUID(uid); + + return OK; +} + status_t StagefrightPlayer::setDataSource( const char *url, const KeyedVector<String8, String8> *headers) { return mPlayer->setDataSource(url, headers); diff --git a/media/libmediaplayerservice/StagefrightPlayer.h b/media/libmediaplayerservice/StagefrightPlayer.h index 85a546dc89e8..cbc6d4996da1 100644 --- a/media/libmediaplayerservice/StagefrightPlayer.h +++ b/media/libmediaplayerservice/StagefrightPlayer.h @@ -31,6 +31,8 @@ public: virtual status_t initCheck(); + virtual status_t setUID(uid_t uid); + virtual status_t setDataSource( const char *url, const KeyedVector<String8, String8> *headers); diff --git a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp index b3b3af5b39a9..5a5330d69feb 100644 --- a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp +++ b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp @@ -35,8 +35,11 @@ namespace android { NuPlayer::HTTPLiveSource::HTTPLiveSource( const char *url, - const KeyedVector<String8, String8> *headers) + const KeyedVector<String8, String8> *headers, + bool uidValid, uid_t uid) : mURL(url), + mUIDValid(uidValid), + mUID(uid), mFlags(0), mEOS(false), mOffset(0) { @@ -65,7 +68,8 @@ void NuPlayer::HTTPLiveSource::start() { mLiveLooper->start(); mLiveSession = new LiveSession( - (mFlags & kFlagIncognito) ? LiveSession::kFlagIncognito : 0); + (mFlags & kFlagIncognito) ? LiveSession::kFlagIncognito : 0, + mUIDValid, mUID); mLiveLooper->registerHandler(mLiveSession); diff --git a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h index 7a337e9aa025..36c67c501b38 100644 --- a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h +++ b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h @@ -29,7 +29,9 @@ struct LiveSession; struct NuPlayer::HTTPLiveSource : public NuPlayer::Source { HTTPLiveSource( const char *url, - const KeyedVector<String8, String8> *headers); + const KeyedVector<String8, String8> *headers, + bool uidValid = false, + uid_t uid = 0); virtual void start(); @@ -54,6 +56,8 @@ private: AString mURL; KeyedVector<String8, String8> mExtraHeaders; + bool mUIDValid; + uid_t mUID; uint32_t mFlags; bool mEOS; off64_t mOffset; diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp index effa7038fe6d..b06f20d55c80 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp +++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp @@ -44,7 +44,8 @@ namespace android { //////////////////////////////////////////////////////////////////////////////// NuPlayer::NuPlayer() - : mAudioEOS(false), + : mUIDValid(false), + mAudioEOS(false), mVideoEOS(false), mScanSourcesPending(false), mScanSourcesGeneration(0), @@ -57,6 +58,11 @@ NuPlayer::NuPlayer() NuPlayer::~NuPlayer() { } +void NuPlayer::setUID(uid_t uid) { + mUIDValid = true; + mUID = uid; +} + void NuPlayer::setDriver(const wp<NuPlayerDriver> &driver) { mDriver = driver; } @@ -72,7 +78,7 @@ void NuPlayer::setDataSource( const char *url, const KeyedVector<String8, String8> *headers) { sp<AMessage> msg = new AMessage(kWhatSetDataSource, id()); - msg->setObject("source", new HTTPLiveSource(url, headers)); + msg->setObject("source", new HTTPLiveSource(url, headers, mUIDValid, mUID)); msg->post(); } diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.h b/media/libmediaplayerservice/nuplayer/NuPlayer.h index fb5b001fb798..cf9185b814d9 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayer.h +++ b/media/libmediaplayerservice/nuplayer/NuPlayer.h @@ -33,6 +33,8 @@ struct NuPlayerDriver; struct NuPlayer : public AHandler { NuPlayer(); + void setUID(uid_t uid); + void setDriver(const wp<NuPlayerDriver> &driver); void setDataSource(const sp<IStreamSource> &source); @@ -84,6 +86,8 @@ private: }; wp<NuPlayerDriver> mDriver; + bool mUIDValid; + uid_t mUID; sp<Source> mSource; sp<NativeWindowWrapper> mNativeWindow; sp<MediaPlayerBase::AudioSink> mAudioSink; diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp index e1213f41c345..7cd8b6cc7f55 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp +++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp @@ -55,6 +55,12 @@ status_t NuPlayerDriver::initCheck() { return OK; } +status_t NuPlayerDriver::setUID(uid_t uid) { + mPlayer->setUID(uid); + + return OK; +} + status_t NuPlayerDriver::setDataSource( const char *url, const KeyedVector<String8, String8> *headers) { CHECK_EQ((int)mState, (int)UNINITIALIZED); diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h index 145fd80b2dda..1bb7ca23f850 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h +++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h @@ -28,6 +28,8 @@ struct NuPlayerDriver : public MediaPlayerInterface { virtual status_t initCheck(); + virtual status_t setUID(uid_t uid); + virtual status_t setDataSource( const char *url, const KeyedVector<String8, String8> *headers); diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp index 167071a39075..d4d07b2815d9 100644 --- a/media/libstagefright/ACodec.cpp +++ b/media/libstagefright/ACodec.cpp @@ -1190,6 +1190,17 @@ bool ACodec::BaseState::onOMXMessage(const sp<AMessage> &msg) { CHECK(msg->findInt32("data1", &data1)); CHECK(msg->findInt32("data2", &data2)); + if (event == OMX_EventCmdComplete + && data1 == OMX_CommandFlush + && data2 == (int32_t)OMX_ALL) { + // Use of this notification is not consistent across + // implementations. We'll drop this notification and rely + // on flush-complete notifications on the individual port + // indices instead. + + return true; + } + return onOMXEvent( static_cast<OMX_EVENTTYPE>(event), static_cast<OMX_U32>(data1), @@ -2119,6 +2130,7 @@ bool ACodec::ExecutingToIdleState::onOMXEvent( return BaseState::onOMXEvent(event, data1, data2); } } + void ACodec::ExecutingToIdleState::changeStateIfWeOwnAllBuffers() { if (mCodec->allYourBuffersAreBelongToUs()) { CHECK_EQ(mCodec->mOMX->sendCommand( diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp index aa7edccb2ffb..77c25d19591b 100644 --- a/media/libstagefright/AwesomePlayer.cpp +++ b/media/libstagefright/AwesomePlayer.cpp @@ -128,6 +128,9 @@ struct AwesomeNativeWindowRenderer : public AwesomeRenderer { } virtual void render(MediaBuffer *buffer) { + int64_t timeUs; + CHECK(buffer->meta_data()->findInt64(kKeyTime, &timeUs)); + native_window_set_buffers_timestamp(mNativeWindow.get(), timeUs * 1000); status_t err = mNativeWindow->queueBuffer( mNativeWindow.get(), buffer->graphicBuffer().get()); if (err != 0) { @@ -180,6 +183,7 @@ void addBatteryData(uint32_t params) { //////////////////////////////////////////////////////////////////////////////// AwesomePlayer::AwesomePlayer() : mQueueStarted(false), + mUIDValid(false), mTimeSource(NULL), mVideoRendererIsPreview(false), mAudioPlayer(NULL), @@ -243,6 +247,13 @@ void AwesomePlayer::setListener(const wp<MediaPlayerBase> &listener) { mListener = listener; } +void AwesomePlayer::setUID(uid_t uid) { + LOGI("AwesomePlayer running on behalf of uid %d", uid); + + mUID = uid; + mUIDValid = true; +} + status_t AwesomePlayer::setDataSource( const char *uri, const KeyedVector<String8, String8> *headers) { Mutex::Autolock autoLock(mLock); @@ -1928,6 +1939,10 @@ status_t AwesomePlayer::finishSetDataSource_l() { ? HTTPBase::kFlagIncognito : 0); + if (mUIDValid) { + mConnectingDataSource->setUID(mUID); + } + mLock.unlock(); status_t err = mConnectingDataSource->connect(mUri, &mUriHeaders); mLock.lock(); @@ -2009,6 +2024,10 @@ status_t AwesomePlayer::finishSetDataSource_l() { mRTSPController = new ARTSPController(mLooper); mConnectingRTSPController = mRTSPController; + if (mUIDValid) { + mConnectingRTSPController->setUID(mUID); + } + mLock.unlock(); status_t err = mRTSPController->connect(mUri.string()); mLock.lock(); diff --git a/media/libstagefright/HTTPBase.cpp b/media/libstagefright/HTTPBase.cpp index c0ae29dc1ac7..0d2455108a23 100644 --- a/media/libstagefright/HTTPBase.cpp +++ b/media/libstagefright/HTTPBase.cpp @@ -37,7 +37,8 @@ HTTPBase::HTTPBase() mTotalTransferBytes(0), mPrevBandwidthMeasureTimeUs(0), mPrevEstimatedBandWidthKbps(0), - mBandWidthCollectFreqMs(5000) { + mBandWidthCollectFreqMs(5000), + mUIDValid(false) { } // static @@ -119,4 +120,19 @@ status_t HTTPBase::setBandwidthStatCollectFreq(int32_t freqMs) { return OK; } +void HTTPBase::setUID(uid_t uid) { + mUIDValid = true; + mUID = uid; +} + +bool HTTPBase::getUID(uid_t *uid) const { + if (!mUIDValid) { + return false; + } + + *uid = mUID; + + return true; +} + } // namespace android diff --git a/media/libstagefright/HTTPStream.cpp b/media/libstagefright/HTTPStream.cpp index a156da69b6cd..d526ebd8bc1a 100644 --- a/media/libstagefright/HTTPStream.cpp +++ b/media/libstagefright/HTTPStream.cpp @@ -43,6 +43,7 @@ const char *HTTPStream::kStatusKey = ":status:"; // MUST be lowercase. HTTPStream::HTTPStream() : mState(READY), + mUIDValid(false), mSocket(-1), mSSLContext(NULL), mSSL(NULL) { @@ -57,6 +58,11 @@ HTTPStream::~HTTPStream() { } } +void HTTPStream::setUID(uid_t uid) { + mUIDValid = true; + mUID = uid; +} + static bool MakeSocketBlocking(int s, bool blocking) { // Make socket non-blocking. int flags = fcntl(s, F_GETFL, 0); @@ -250,6 +256,10 @@ status_t HTTPStream::connect(const char *server, int port, bool https) { continue; } + if (mUIDValid) { + RegisterSocketUser(mSocket, mUID); + } + setReceiveTimeout(30); // Time out reads after 30 secs by default. int s = mSocket; @@ -596,5 +606,18 @@ void HTTPStream::setReceiveTimeout(int seconds) { CHECK_EQ(0, setsockopt(mSocket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv))); } +// static +void HTTPStream::RegisterSocketUser(int s, uid_t uid) { + // Lower bits MUST be 0. + static const uint64_t kTag = 0xdeadbeef00000000ll; + + AString line = StringPrintf("t %d %llu %d", s, kTag, uid); + + int fd = open("/proc/net/xt_qtaguid/ctrl", O_WRONLY); + write(fd, line.c_str(), line.size()); + close(fd); + fd = -1; +} + } // namespace android diff --git a/media/libstagefright/NuHTTPDataSource.cpp b/media/libstagefright/NuHTTPDataSource.cpp index dac2ee4766d0..29497673fbd4 100644 --- a/media/libstagefright/NuHTTPDataSource.cpp +++ b/media/libstagefright/NuHTTPDataSource.cpp @@ -140,6 +140,11 @@ status_t NuHTTPDataSource::connect( return ERROR_MALFORMED; } + uid_t uid; + if (getUID(&uid)) { + mHTTP.setUID(uid); + } + return connect(host, port, path, https, headers, offset); } diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp index e36b01fbd7a8..1ac2c1fbbfb4 100644 --- a/media/libstagefright/OMXCodec.cpp +++ b/media/libstagefright/OMXCodec.cpp @@ -477,6 +477,15 @@ sp<MediaSource> OMXCodec::Create( const char *matchComponentName, uint32_t flags, const sp<ANativeWindow> &nativeWindow) { + int32_t requiresSecureBuffers; + if (source->getFormat()->findInt32( + kKeyRequiresSecureBuffers, + &requiresSecureBuffers) + && requiresSecureBuffers) { + flags |= kIgnoreCodecSpecificData; + flags |= kUseSecureInputBuffers; + } + const char *mime; bool success = meta->findCString(kKeyMIMEType, &mime); CHECK(success); @@ -530,17 +539,17 @@ sp<MediaSource> OMXCodec::Create( LOGV("Successfully allocated OMX node '%s'", componentName); sp<OMXCodec> codec = new OMXCodec( - omx, node, quirks, + omx, node, quirks, flags, createEncoder, mime, componentName, source, nativeWindow); observer->setCodec(codec); - err = codec->configureCodec(meta, flags); + err = codec->configureCodec(meta); if (err == OK) { if (!strcmp("OMX.Nvidia.mpeg2v.decode", componentName)) { - codec->mOnlySubmitOneBufferAtOneTime = true; + codec->mFlags |= kOnlySubmitOneInputBufferAtOneTime; } return codec; @@ -553,24 +562,11 @@ sp<MediaSource> OMXCodec::Create( return NULL; } -status_t OMXCodec::configureCodec(const sp<MetaData> &meta, uint32_t flags) { - mIsMetaDataStoredInVideoBuffers = false; - if (flags & kStoreMetaDataInVideoBuffers) { - mIsMetaDataStoredInVideoBuffers = true; - } - - mOnlySubmitOneBufferAtOneTime = false; - if (flags & kOnlySubmitOneInputBufferAtOneTime) { - mOnlySubmitOneBufferAtOneTime = true; - } +status_t OMXCodec::configureCodec(const sp<MetaData> &meta) { + LOGV("configureCodec protected=%d", + (mFlags & kEnableGrallocUsageProtected) ? 1 : 0); - mEnableGrallocUsageProtected = false; - if (flags & kEnableGrallocUsageProtected) { - mEnableGrallocUsageProtected = true; - } - LOGV("configureCodec protected=%d", mEnableGrallocUsageProtected); - - if (!(flags & kIgnoreCodecSpecificData)) { + if (!(mFlags & kIgnoreCodecSpecificData)) { uint32_t type; const void *data; size_t size; @@ -745,7 +741,7 @@ status_t OMXCodec::configureCodec(const sp<MetaData> &meta, uint32_t flags) { initOutputFormat(meta); - if ((flags & kClientNeedsFramebuffer) + if ((mFlags & kClientNeedsFramebuffer) && !strncmp(mComponentName, "OMX.SEC.", 8)) { OMX_INDEXTYPE index; @@ -1468,7 +1464,8 @@ status_t OMXCodec::setVideoOutputFormat( } OMXCodec::OMXCodec( - const sp<IOMX> &omx, IOMX::node_id node, uint32_t quirks, + const sp<IOMX> &omx, IOMX::node_id node, + uint32_t quirks, uint32_t flags, bool isEncoder, const char *mime, const char *componentName, @@ -1478,6 +1475,7 @@ OMXCodec::OMXCodec( mOMXLivesLocally(omx->livesLocally(getpid())), mNode(node), mQuirks(quirks), + mFlags(flags), mIsEncoder(isEncoder), mMIME(strdup(mime)), mComponentName(strdup(componentName)), @@ -1645,13 +1643,14 @@ status_t OMXCodec::allocateBuffersOnPort(OMX_U32 portIndex) { return allocateOutputBuffersFromNativeWindow(); } - if (mEnableGrallocUsageProtected && portIndex == kPortIndexOutput) { + if ((mFlags & kEnableGrallocUsageProtected) && portIndex == kPortIndexOutput) { LOGE("protected output buffers must be stent to an ANativeWindow"); return PERMISSION_DENIED; } status_t err = OK; - if (mIsMetaDataStoredInVideoBuffers && portIndex == kPortIndexInput) { + if ((mFlags & kStoreMetaDataInVideoBuffers) + && portIndex == kPortIndexInput) { err = mOMX->storeMetaDataInBuffers(mNode, kPortIndexInput, OMX_TRUE); if (err != OK) { LOGE("Storing meta data in video buffers is not supported"); @@ -1687,7 +1686,8 @@ status_t OMXCodec::allocateBuffersOnPort(OMX_U32 portIndex) { IOMX::buffer_id buffer; if (portIndex == kPortIndexInput - && (mQuirks & kRequiresAllocateBufferOnInputPorts)) { + && ((mQuirks & kRequiresAllocateBufferOnInputPorts) + || (mFlags & kUseSecureInputBuffers))) { if (mOMXLivesLocally) { mem.clear(); @@ -1748,6 +1748,31 @@ status_t OMXCodec::allocateBuffersOnPort(OMX_U32 portIndex) { // dumpPortStatus(portIndex); + if (portIndex == kPortIndexInput && (mFlags & kUseSecureInputBuffers)) { + Vector<MediaBuffer *> buffers; + for (size_t i = 0; i < def.nBufferCountActual; ++i) { + const BufferInfo &info = mPortBuffers[kPortIndexInput].itemAt(i); + + MediaBuffer *mbuf = new MediaBuffer(info.mData, info.mSize); + buffers.push(mbuf); + } + + status_t err = mSource->setBuffers(buffers); + + if (err != OK) { + for (size_t i = 0; i < def.nBufferCountActual; ++i) { + buffers.editItemAt(i)->release(); + } + buffers.clear(); + + CODEC_LOGE( + "Codec requested to use secure input buffers but " + "upstream source didn't support that."); + + return err; + } + } + return OK; } @@ -1815,7 +1840,7 @@ status_t OMXCodec::allocateOutputBuffersFromNativeWindow() { // XXX: Currently this error is logged, but not fatal. usage = 0; } - if (mEnableGrallocUsageProtected) { + if (mFlags & kEnableGrallocUsageProtected) { usage |= GRALLOC_USAGE_PROTECTED; } @@ -2067,7 +2092,12 @@ void OMXCodec::on_message(const omx_message &msg) { } else if (mState != ERROR && mPortStatus[kPortIndexInput] != SHUTTING_DOWN) { CHECK_EQ((int)mPortStatus[kPortIndexInput], (int)ENABLED); - drainInputBuffer(&buffers->editItemAt(i)); + + if (mFlags & kUseSecureInputBuffers) { + drainAnyInputBuffer(); + } else { + drainInputBuffer(&buffers->editItemAt(i)); + } } break; } @@ -2804,32 +2834,81 @@ void OMXCodec::fillOutputBuffers() { void OMXCodec::drainInputBuffers() { CHECK(mState == EXECUTING || mState == RECONFIGURING); - Vector<BufferInfo> *buffers = &mPortBuffers[kPortIndexInput]; - for (size_t i = 0; i < buffers->size(); ++i) { - BufferInfo *info = &buffers->editItemAt(i); + if (mFlags & kUseSecureInputBuffers) { + Vector<BufferInfo> *buffers = &mPortBuffers[kPortIndexInput]; + for (size_t i = 0; i < buffers->size(); ++i) { + if (!drainAnyInputBuffer() + || (mFlags & kOnlySubmitOneInputBufferAtOneTime)) { + break; + } + } + } else { + Vector<BufferInfo> *buffers = &mPortBuffers[kPortIndexInput]; + for (size_t i = 0; i < buffers->size(); ++i) { + BufferInfo *info = &buffers->editItemAt(i); - if (info->mStatus != OWNED_BY_US) { - continue; + if (info->mStatus != OWNED_BY_US) { + continue; + } + + if (!drainInputBuffer(info)) { + break; + } + + if (mFlags & kOnlySubmitOneInputBufferAtOneTime) { + break; + } } + } +} - if (!drainInputBuffer(info)) { - break; +bool OMXCodec::drainAnyInputBuffer() { + return drainInputBuffer((BufferInfo *)NULL); +} + +OMXCodec::BufferInfo *OMXCodec::findInputBufferByDataPointer(void *ptr) { + Vector<BufferInfo> *infos = &mPortBuffers[kPortIndexInput]; + for (size_t i = 0; i < infos->size(); ++i) { + BufferInfo *info = &infos->editItemAt(i); + + if (info->mData == ptr) { + CODEC_LOGV( + "input buffer data ptr = %p, buffer_id = %p", + ptr, + info->mBuffer); + + return info; } + } - if (mOnlySubmitOneBufferAtOneTime) { - break; + TRESPASS(); +} + +OMXCodec::BufferInfo *OMXCodec::findEmptyInputBuffer() { + Vector<BufferInfo> *infos = &mPortBuffers[kPortIndexInput]; + for (size_t i = 0; i < infos->size(); ++i) { + BufferInfo *info = &infos->editItemAt(i); + + if (info->mStatus == OWNED_BY_US) { + return info; } } + + TRESPASS(); } bool OMXCodec::drainInputBuffer(BufferInfo *info) { - CHECK_EQ((int)info->mStatus, (int)OWNED_BY_US); + if (info != NULL) { + CHECK_EQ((int)info->mStatus, (int)OWNED_BY_US); + } if (mSignalledEOS) { return false; } if (mCodecSpecificDataIndex < mCodecSpecificData.size()) { + CHECK(!(mFlags & kUseSecureInputBuffers)); + const CodecSpecificData *specific = mCodecSpecificData[mCodecSpecificDataIndex]; @@ -2925,6 +3004,11 @@ bool OMXCodec::drainInputBuffer(BufferInfo *info) { break; } + if (mFlags & kUseSecureInputBuffers) { + info = findInputBufferByDataPointer(srcBuffer->data()); + CHECK(info != NULL); + } + size_t remainingBytes = info->mSize - offset; if (srcBuffer->range_length() > remainingBytes) { @@ -2960,14 +3044,24 @@ bool OMXCodec::drainInputBuffer(BufferInfo *info) { releaseBuffer = false; info->mMediaBuffer = srcBuffer; } else { - if (mIsMetaDataStoredInVideoBuffers) { + if (mFlags & kStoreMetaDataInVideoBuffers) { releaseBuffer = false; info->mMediaBuffer = srcBuffer; } - memcpy((uint8_t *)info->mData + offset, - (const uint8_t *)srcBuffer->data() - + srcBuffer->range_offset(), - srcBuffer->range_length()); + + if (mFlags & kUseSecureInputBuffers) { + // Data in "info" is already provided at this time. + + releaseBuffer = false; + + CHECK(info->mMediaBuffer == NULL); + info->mMediaBuffer = srcBuffer; + } else { + memcpy((uint8_t *)info->mData + offset, + (const uint8_t *)srcBuffer->data() + + srcBuffer->range_offset(), + srcBuffer->range_length()); + } } int64_t lastBufferTimeUs; @@ -3036,6 +3130,16 @@ bool OMXCodec::drainInputBuffer(BufferInfo *info) { info->mBuffer, offset, timestampUs, timestampUs / 1E6); + if (info == NULL) { + CHECK(mFlags & kUseSecureInputBuffers); + CHECK(signalEOS); + + // This is fishy, there's still a MediaBuffer corresponding to this + // info available to the source at this point even though we're going + // to use it to signal EOS to the codec. + info = findEmptyInputBuffer(); + } + err = mOMX->emptyBuffer( mNode, info->mBuffer, 0, offset, flags, timestampUs); diff --git a/media/libstagefright/WVMExtractor.cpp b/media/libstagefright/WVMExtractor.cpp index 7072d58d6cce..26eda0c4cfb1 100644 --- a/media/libstagefright/WVMExtractor.cpp +++ b/media/libstagefright/WVMExtractor.cpp @@ -33,25 +33,26 @@ #include <utils/Errors.h> +/* The extractor lifetime is short - just long enough to get + * the media sources constructed - so the shared lib needs to remain open + * beyond the lifetime of the extractor. So keep the handle as a global + * rather than a member of the extractor + */ +void *gVendorLibHandle = NULL; + namespace android { -Mutex WVMExtractor::sMutex; -uint32_t WVMExtractor::sActiveExtractors = 0; -void *WVMExtractor::sVendorLibHandle = NULL; +static Mutex gWVMutex; WVMExtractor::WVMExtractor(const sp<DataSource> &source) : mDataSource(source) { { - Mutex::Autolock autoLock(sMutex); - - if (sVendorLibHandle == NULL) { - CHECK(sActiveExtractors == 0); - sVendorLibHandle = dlopen("libwvm.so", RTLD_NOW); + Mutex::Autolock autoLock(gWVMutex); + if (gVendorLibHandle == NULL) { + gVendorLibHandle = dlopen("libwvm.so", RTLD_NOW); } - sActiveExtractors++; - - if (sVendorLibHandle == NULL) { + if (gVendorLibHandle == NULL) { LOGE("Failed to open libwvm.so"); return; } @@ -59,7 +60,7 @@ WVMExtractor::WVMExtractor(const sp<DataSource> &source) typedef WVMLoadableExtractor *(*GetInstanceFunc)(sp<DataSource>); GetInstanceFunc getInstanceFunc = - (GetInstanceFunc) dlsym(sVendorLibHandle, + (GetInstanceFunc) dlsym(gVendorLibHandle, "_ZN7android11GetInstanceENS_2spINS_10DataSourceEEE"); if (getInstanceFunc) { @@ -71,17 +72,6 @@ WVMExtractor::WVMExtractor(const sp<DataSource> &source) } WVMExtractor::~WVMExtractor() { - Mutex::Autolock autoLock(sMutex); - - CHECK(sActiveExtractors > 0); - sActiveExtractors--; - - // Close lib after last use - if (sActiveExtractors == 0) { - if (sVendorLibHandle != NULL) - dlclose(sVendorLibHandle); - sVendorLibHandle = NULL; - } } size_t WVMExtractor::countTracks() { diff --git a/media/libstagefright/chromium_http/support.cpp b/media/libstagefright/chromium_http/support.cpp index 967f126c8adf..f4b36688f4cd 100644 --- a/media/libstagefright/chromium_http/support.cpp +++ b/media/libstagefright/chromium_http/support.cpp @@ -115,31 +115,31 @@ SfRequestContext::SfRequestContext() { mUserAgent = ua.c_str(); - net_log_ = new SfNetLog; + set_net_log(new SfNetLog()); - host_resolver_ = + set_host_resolver( net::CreateSystemHostResolver( net::HostResolver::kDefaultParallelism, NULL /* resolver_proc */, - net_log_); + net_log())); - ssl_config_service_ = - net::SSLConfigService::CreateSystemSSLConfigService(); + set_ssl_config_service( + net::SSLConfigService::CreateSystemSSLConfigService()); - proxy_service_ = net::ProxyService::CreateWithoutProxyResolver( - new net::ProxyConfigServiceAndroid, net_log_); + set_proxy_service(net::ProxyService::CreateWithoutProxyResolver( + new net::ProxyConfigServiceAndroid, net_log())); - http_transaction_factory_ = new net::HttpCache( - host_resolver_, + set_http_transaction_factory(new net::HttpCache( + host_resolver(), new net::CertVerifier(), - dnsrr_resolver_, - dns_cert_checker_.get(), - proxy_service_.get(), - ssl_config_service_.get(), - net::HttpAuthHandlerFactory::CreateDefault(host_resolver_), - network_delegate_, - net_log_, - NULL); // backend_factory + dnsrr_resolver(), + dns_cert_checker(), + proxy_service(), + ssl_config_service(), + net::HttpAuthHandlerFactory::CreateDefault(host_resolver()), + network_delegate(), + net_log(), + NULL)); // backend_factory } const std::string &SfRequestContext::GetUserAgent(const GURL &url) const { diff --git a/media/libstagefright/codecs/avc/enc/AVCEncoder.cpp b/media/libstagefright/codecs/avc/enc/AVCEncoder.cpp index e3292e63c679..009676011714 100644 --- a/media/libstagefright/codecs/avc/enc/AVCEncoder.cpp +++ b/media/libstagefright/codecs/avc/enc/AVCEncoder.cpp @@ -475,7 +475,9 @@ status_t AVCEncoder::read( } status_t err = mSource->read(&mInputBuffer, options); if (err != OK) { - LOGE("Failed to read input video frame: %d", err); + if (err != ERROR_END_OF_STREAM) { + LOGE("Failed to read input video frame: %d", err); + } outputBuffer->release(); return err; } diff --git a/media/libstagefright/codecs/m4v_h263/enc/M4vH263Encoder.cpp b/media/libstagefright/codecs/m4v_h263/enc/M4vH263Encoder.cpp index 15ed2193ae61..d7249c1ebb4f 100644 --- a/media/libstagefright/codecs/m4v_h263/enc/M4vH263Encoder.cpp +++ b/media/libstagefright/codecs/m4v_h263/enc/M4vH263Encoder.cpp @@ -398,10 +398,13 @@ status_t M4vH263Encoder::read( } // Ready for accepting an input video frame - if (OK != mSource->read(&mInputBuffer, options)) { - LOGE("Failed to read from data source"); + status_t err = mSource->read(&mInputBuffer, options); + if (OK != err) { + if (err != ERROR_END_OF_STREAM) { + LOGE("Failed to read from data source"); + } outputBuffer->release(); - return UNKNOWN_ERROR; + return err; } if (mInputBuffer->size() - ((mVideoWidth * mVideoHeight * 3) >> 1) != 0) { diff --git a/media/libstagefright/httplive/LiveSession.cpp b/media/libstagefright/httplive/LiveSession.cpp index f1a2a60b9495..8ecc17ce5580 100644 --- a/media/libstagefright/httplive/LiveSession.cpp +++ b/media/libstagefright/httplive/LiveSession.cpp @@ -41,8 +41,10 @@ namespace android { const int64_t LiveSession::kMaxPlaylistAgeUs = 15000000ll; -LiveSession::LiveSession(uint32_t flags) +LiveSession::LiveSession(uint32_t flags, bool uidValid, uid_t uid) : mFlags(flags), + mUIDValid(uidValid), + mUID(uid), mDataSource(new LiveDataSource), mHTTPDataSource( HTTPBase::Create( @@ -58,6 +60,9 @@ LiveSession::LiveSession(uint32_t flags) mSeekDone(false), mDisconnectPending(false), mMonitorQueueGeneration(0) { + if (mUIDValid) { + mHTTPDataSource->setUID(mUID); + } } LiveSession::~LiveSession() { @@ -671,6 +676,10 @@ status_t LiveSession::decryptBuffer( ? HTTPBase::kFlagIncognito : 0); + if (mUIDValid) { + keySource->setUID(mUID); + } + status_t err = keySource->connect(keyURI.c_str()); if (err == OK) { diff --git a/media/libstagefright/include/ARTSPController.h b/media/libstagefright/include/ARTSPController.h index ce7ffe5c2352..2bd5be632a26 100644 --- a/media/libstagefright/include/ARTSPController.h +++ b/media/libstagefright/include/ARTSPController.h @@ -30,6 +30,8 @@ struct MyHandler; struct ARTSPController : public MediaExtractor { ARTSPController(const sp<ALooper> &looper); + void setUID(uid_t uid); + status_t connect(const char *url); void disconnect(); @@ -80,6 +82,9 @@ private: sp<MyHandler> mHandler; sp<AHandlerReflector<ARTSPController> > mReflector; + bool mUIDValid; + uid_t mUID; + void (*mSeekDoneCb)(void *); void *mSeekDoneCookie; int64_t mLastSeekCompletedTimeUs; diff --git a/media/libstagefright/include/AwesomePlayer.h b/media/libstagefright/include/AwesomePlayer.h index f6df3801c2fe..e069b4dbaf65 100644 --- a/media/libstagefright/include/AwesomePlayer.h +++ b/media/libstagefright/include/AwesomePlayer.h @@ -62,6 +62,7 @@ struct AwesomePlayer { ~AwesomePlayer(); void setListener(const wp<MediaPlayerBase> &listener); + void setUID(uid_t uid); status_t setDataSource( const char *uri, @@ -150,6 +151,8 @@ private: TimedEventQueue mQueue; bool mQueueStarted; wp<MediaPlayerBase> mListener; + bool mUIDValid; + uid_t mUID; sp<Surface> mSurface; sp<ANativeWindow> mNativeWindow; diff --git a/media/libstagefright/include/HTTPBase.h b/media/libstagefright/include/HTTPBase.h index 3a7fbb690d16..2e25dd918086 100644 --- a/media/libstagefright/include/HTTPBase.h +++ b/media/libstagefright/include/HTTPBase.h @@ -48,13 +48,15 @@ struct HTTPBase : public DataSource { virtual status_t setBandwidthStatCollectFreq(int32_t freqMs); + void setUID(uid_t uid); + bool getUID(uid_t *uid) const; + static sp<HTTPBase> Create(uint32_t flags = 0); protected: void addBandwidthMeasurement(size_t numBytes, int64_t delayUs); private: - struct BandwidthEntry { int64_t mDelayUs; size_t mNumBytes; @@ -76,6 +78,8 @@ private: int32_t mPrevEstimatedBandWidthKbps; int32_t mBandWidthCollectFreqMs; + bool mUIDValid; + uid_t mUID; DISALLOW_EVIL_CONSTRUCTORS(HTTPBase); }; diff --git a/media/libstagefright/include/HTTPStream.h b/media/libstagefright/include/HTTPStream.h index 09e6a5fbdf52..88ba9d6e1435 100644 --- a/media/libstagefright/include/HTTPStream.h +++ b/media/libstagefright/include/HTTPStream.h @@ -32,6 +32,8 @@ public: HTTPStream(); ~HTTPStream(); + void setUID(uid_t uid); + status_t connect(const char *server, int port = -1, bool https = false); status_t disconnect(); @@ -58,6 +60,8 @@ public: // _excluding_ the termianting CRLF. status_t receive_line(char *line, size_t size); + static void RegisterSocketUser(int s, uid_t uid); + private: enum State { READY, @@ -67,6 +71,10 @@ private: State mState; Mutex mLock; + + bool mUIDValid; + uid_t mUID; + int mSocket; KeyedVector<AString, AString> mHeaders; diff --git a/media/libstagefright/include/LiveSession.h b/media/libstagefright/include/LiveSession.h index 99abe64c2fea..188ef5e605b3 100644 --- a/media/libstagefright/include/LiveSession.h +++ b/media/libstagefright/include/LiveSession.h @@ -35,7 +35,7 @@ struct LiveSession : public AHandler { // Don't log any URLs. kFlagIncognito = 1, }; - LiveSession(uint32_t flags = 0); + LiveSession(uint32_t flags = 0, bool uidValid = false, uid_t uid = 0); sp<DataSource> getDataSource(); @@ -77,6 +77,8 @@ private: }; uint32_t mFlags; + bool mUIDValid; + uid_t mUID; sp<LiveDataSource> mDataSource; diff --git a/media/libstagefright/include/WVMExtractor.h b/media/libstagefright/include/WVMExtractor.h index 0817babb45e0..deecd2543a92 100644 --- a/media/libstagefright/include/WVMExtractor.h +++ b/media/libstagefright/include/WVMExtractor.h @@ -18,7 +18,6 @@ #define WVM_EXTRACTOR_H_ -#include <media/stagefright/DataSource.h> #include <media/stagefright/MediaExtractor.h> #include <utils/Errors.h> @@ -68,10 +67,6 @@ private: WVMExtractor(const WVMExtractor &); WVMExtractor &operator=(const WVMExtractor &); - - static Mutex sMutex; - static uint32_t sActiveExtractors; - static void *sVendorLibHandle; }; } // namespace android diff --git a/media/libstagefright/rtsp/ARTSPConnection.cpp b/media/libstagefright/rtsp/ARTSPConnection.cpp index c4e0cdcab5d3..072d6b2fd46d 100644 --- a/media/libstagefright/rtsp/ARTSPConnection.cpp +++ b/media/libstagefright/rtsp/ARTSPConnection.cpp @@ -34,13 +34,17 @@ #include <openssl/md5.h> #include <sys/socket.h> +#include "HTTPStream.h" + namespace android { // static const int64_t ARTSPConnection::kSelectTimeoutUs = 1000ll; -ARTSPConnection::ARTSPConnection() - : mState(DISCONNECTED), +ARTSPConnection::ARTSPConnection(bool uidValid, uid_t uid) + : mUIDValid(uidValid), + mUID(uid), + mState(DISCONNECTED), mAuthType(NONE), mSocket(-1), mConnectionID(0), @@ -246,6 +250,10 @@ void ARTSPConnection::onConnect(const sp<AMessage> &msg) { mSocket = socket(AF_INET, SOCK_STREAM, 0); + if (mUIDValid) { + HTTPStream::RegisterSocketUser(mSocket, mUID); + } + MakeSocketBlocking(mSocket, false); struct sockaddr_in remote; diff --git a/media/libstagefright/rtsp/ARTSPConnection.h b/media/libstagefright/rtsp/ARTSPConnection.h index ac2e3ae1b856..5cb84fd08f96 100644 --- a/media/libstagefright/rtsp/ARTSPConnection.h +++ b/media/libstagefright/rtsp/ARTSPConnection.h @@ -33,7 +33,7 @@ struct ARTSPResponse : public RefBase { }; struct ARTSPConnection : public AHandler { - ARTSPConnection(); + ARTSPConnection(bool uidValid = false, uid_t uid = 0); void connect(const char *url, const sp<AMessage> &reply); void disconnect(const sp<AMessage> &reply); @@ -74,6 +74,8 @@ private: static const int64_t kSelectTimeoutUs; + bool mUIDValid; + uid_t mUID; State mState; AString mUser, mPass; AuthType mAuthType; diff --git a/media/libstagefright/rtsp/ARTSPController.cpp b/media/libstagefright/rtsp/ARTSPController.cpp index 1328d2ec595c..2ebae7e37abc 100644 --- a/media/libstagefright/rtsp/ARTSPController.cpp +++ b/media/libstagefright/rtsp/ARTSPController.cpp @@ -28,6 +28,7 @@ namespace android { ARTSPController::ARTSPController(const sp<ALooper> &looper) : mState(DISCONNECTED), mLooper(looper), + mUIDValid(false), mSeekDoneCb(NULL), mSeekDoneCookie(NULL), mLastSeekCompletedTimeUs(-1) { @@ -40,6 +41,11 @@ ARTSPController::~ARTSPController() { mLooper->unregisterHandler(mReflector->id()); } +void ARTSPController::setUID(uid_t uid) { + mUIDValid = true; + mUID = uid; +} + status_t ARTSPController::connect(const char *url) { Mutex::Autolock autoLock(mLock); @@ -49,7 +55,7 @@ status_t ARTSPController::connect(const char *url) { sp<AMessage> msg = new AMessage(kWhatConnectDone, mReflector->id()); - mHandler = new MyHandler(url, mLooper); + mHandler = new MyHandler(url, mLooper, mUIDValid, mUID); mState = CONNECTING; diff --git a/media/libstagefright/rtsp/MyHandler.h b/media/libstagefright/rtsp/MyHandler.h index d15d9c5c0966..3188959564a0 100644 --- a/media/libstagefright/rtsp/MyHandler.h +++ b/media/libstagefright/rtsp/MyHandler.h @@ -40,6 +40,8 @@ #include <sys/socket.h> #include <netdb.h> +#include "HTTPStream.h" + // If no access units are received within 5 secs, assume that the rtp // stream has ended and signal end of stream. static int64_t kAccessUnitTimeoutUs = 5000000ll; @@ -92,10 +94,14 @@ static bool GetAttribute(const char *s, const char *key, AString *value) { } struct MyHandler : public AHandler { - MyHandler(const char *url, const sp<ALooper> &looper) - : mLooper(looper), + MyHandler( + const char *url, const sp<ALooper> &looper, + bool uidValid = false, uid_t uid = 0) + : mUIDValid(uidValid), + mUID(uid), + mLooper(looper), mNetLooper(new ALooper), - mConn(new ARTSPConnection), + mConn(new ARTSPConnection(mUIDValid, mUID)), mRTPConn(new ARTPConnection), mOriginalSessionURL(url), mSessionURL(url), @@ -1078,6 +1084,8 @@ private: List<sp<ABuffer> > mPackets; }; + bool mUIDValid; + uid_t mUID; sp<ALooper> mLooper; sp<ALooper> mNetLooper; sp<ARTSPConnection> mConn; @@ -1172,6 +1180,11 @@ private: ARTPConnection::MakePortPair( &info->mRTPSocket, &info->mRTCPSocket, &rtpPort); + if (mUIDValid) { + HTTPStream::RegisterSocketUser(info->mRTPSocket, mUID); + HTTPStream::RegisterSocketUser(info->mRTCPSocket, mUID); + } + request.append("Transport: RTP/AVP/UDP;unicast;client_port="); request.append(rtpPort); request.append("-"); diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 6d8eab63c074..f42cbbfc4f7a 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -25,6 +25,11 @@ android:exported="true" /> + <!-- started from PhoneWindowManager + TODO: Should have an android:permission attribute --> + <service android:name=".screenshot.TakeScreenshotService" + android:exported="false" /> + <activity android:name=".usb.UsbPreferenceActivity" android:theme="@*android:style/Theme.Holo.Dialog.Alert" android:excludeFromRecents="true"> diff --git a/packages/SystemUI/res/drawable-hdpi/global_screenshot_background.9.png b/packages/SystemUI/res/drawable-hdpi/global_screenshot_background.9.png Binary files differnew file mode 100644 index 000000000000..e14111dae168 --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/global_screenshot_background.9.png diff --git a/packages/SystemUI/res/drawable-mdpi/global_screenshot_background.9.png b/packages/SystemUI/res/drawable-mdpi/global_screenshot_background.9.png Binary files differnew file mode 100644 index 000000000000..e14111dae168 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/global_screenshot_background.9.png diff --git a/packages/SystemUI/res/layout/global_screenshot.xml b/packages/SystemUI/res/layout/global_screenshot.xml new file mode 100644 index 000000000000..6cb8799eaeb3 --- /dev/null +++ b/packages/SystemUI/res/layout/global_screenshot.xml @@ -0,0 +1,40 @@ +<?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. +--> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <ImageView android:id="@+id/global_screenshot_background" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="#FF000000" + android:visibility="gone" /> + <FrameLayout + android:id="@+id/global_screenshot_container" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@drawable/global_screenshot_background" + android:visibility="gone"> + <ImageView android:id="@+id/global_screenshot" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:adjustViewBounds="true" /> + </FrameLayout> + <ImageView android:id="@+id/global_screenshot_flash" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="#FFFFFFFF" + android:visibility="gone" /> +</FrameLayout> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 7a4ac5d816f6..5298f2e66927 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -36,10 +36,6 @@ <!-- Whether or not we show the number in the bar. --> <bool name="config_statusBarShowNumber">true</bool> - <!-- Whether a software navigation bar should be shown. NOTE: in the future this may be - autodetected from the Configuration. --> - <bool name="config_showNavigationBar">false</bool> - <!-- How many icons may be shown at once in the system bar. Includes any slots that may be reused for things like IME control. --> <integer name="config_maxNotificationIcons">5</integer> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 86e0cd014302..70f9b7521959 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -165,4 +165,9 @@ <string name="use_ptp_button_title">Mount as a camera (PTP)</string> <!-- Label for the installer CD image option in UsbPreferenceActivity. [CHAR LIMIT=50] --> <string name="installer_cd_button_title">Install Android File Transfer application for Mac</string> + + <!-- toast message displayed when a screenshot is saved to the Gallery. --> + <string name="screenshot_saving_toast">Screenshot saved to Gallery</string> + <!-- toast message displayed when we fail to take a screenshot. --> + <string name="screenshot_failed_toast">Could not save screenshot</string> </resources> diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java new file mode 100644 index 000000000000..83a5578cfa36 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -0,0 +1,384 @@ +/* + * 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. + */ + +package com.android.systemui.screenshot; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.TimeInterpolator; +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.app.Activity; +import android.content.ContentValues; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.PixelFormat; +import android.media.MediaScannerConnection; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Binder; +import android.os.Environment; +import android.os.ServiceManager; +import android.provider.MediaStore; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.Display; +import android.view.IWindowManager; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.Surface; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import com.android.systemui.R; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.Thread; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * POD used in the AsyncTask which saves an image in the background. + */ +class SaveImageInBackgroundData { + Context context; + Bitmap image; + int result; +} + +/** + * An AsyncTask that saves an image to the media store in the background. + */ +class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Void, + SaveImageInBackgroundData> { + private static final String TAG = "SaveImageInBackgroundTask"; + private static final String SCREENSHOTS_DIR_NAME = "Screenshots"; + private static final String SCREENSHOT_FILE_PATH_TEMPLATE = "%s/%s/Screenshot_%s-%d.png"; + + @Override + protected SaveImageInBackgroundData doInBackground(SaveImageInBackgroundData... params) { + if (params.length != 1) return null; + + Context context = params[0].context; + Bitmap image = params[0].image; + + try{ + long currentTime = System.currentTimeMillis(); + String date = new SimpleDateFormat("MM-dd-yy-kk-mm-ss").format(new Date(currentTime)); + String imageDir = Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_PICTURES).getAbsolutePath(); + String imageFilePath = String.format(SCREENSHOT_FILE_PATH_TEMPLATE, + imageDir, SCREENSHOTS_DIR_NAME, + date, currentTime % 1000); + + // Save the screenshot to the MediaStore + ContentValues values = new ContentValues(); + values.put(MediaStore.Images.ImageColumns.DATA, imageFilePath); + values.put(MediaStore.Images.ImageColumns.TITLE, "Screenshot"); + values.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, "Screenshot"); + values.put(MediaStore.Images.ImageColumns.DATE_TAKEN, currentTime); + values.put(MediaStore.Images.ImageColumns.DATE_ADDED, currentTime); + values.put(MediaStore.Images.ImageColumns.DATE_MODIFIED, currentTime); + values.put(MediaStore.Images.ImageColumns.MIME_TYPE, "image/png"); + Uri uri = context.getContentResolver().insert( + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); + + OutputStream out = context.getContentResolver().openOutputStream(uri); + image.compress(Bitmap.CompressFormat.PNG, 100, out); + out.flush(); + out.close(); + + params[0].result = 0; + }catch(IOException e){ + params[0].result = 1; + } + + return params[0]; + }; + + @Override + protected void onPostExecute(SaveImageInBackgroundData params) { + if (params.result > 0) { + // Show a message that we've failed to save the image to disk + Toast.makeText(params.context, R.string.screenshot_failed_toast, + Toast.LENGTH_SHORT).show(); + } else { + // Show a message that we've saved the screenshot to disk + Toast.makeText(params.context, R.string.screenshot_saving_toast, + Toast.LENGTH_SHORT).show(); + } + }; +} + +/** + * TODO: + * - Performance when over gl surfaces? Ie. Gallery + * - what do we say in the Toast? Which icon do we get if the user uses another + * type of gallery? + */ +class GlobalScreenshot { + private static final String TAG = "GlobalScreenshot"; + private static final int SCREENSHOT_FADE_IN_DURATION = 900; + private static final int SCREENSHOT_FADE_OUT_DELAY = 1000; + private static final int SCREENSHOT_FADE_OUT_DURATION = 450; + private static final int TOAST_FADE_IN_DURATION = 500; + private static final int TOAST_FADE_OUT_DELAY = 1000; + private static final int TOAST_FADE_OUT_DURATION = 500; + private static final float BACKGROUND_ALPHA = 0.65f; + private static final float SCREENSHOT_SCALE = 0.85f; + private static final float SCREENSHOT_MIN_SCALE = 0.7f; + private static final float SCREENSHOT_ROTATION = -6.75f; // -12.5f; + + private Context mContext; + private LayoutInflater mLayoutInflater; + private IWindowManager mIWindowManager; + private WindowManager mWindowManager; + private WindowManager.LayoutParams mWindowLayoutParams; + private Display mDisplay; + private DisplayMetrics mDisplayMetrics; + private Matrix mDisplayMatrix; + + private Bitmap mScreenBitmap; + private View mScreenshotLayout; + private ImageView mBackgroundView; + private FrameLayout mScreenshotContainerView; + private ImageView mScreenshotView; + + private AnimatorSet mScreenshotAnimation; + + // General use cubic interpolator + final TimeInterpolator mCubicInterpolator = new TimeInterpolator() { + public float getInterpolation(float t) { + return t*t*t; + } + }; + // The interpolator used to control the background alpha at the start of the animation + final TimeInterpolator mBackgroundViewAlphaInterpolator = new TimeInterpolator() { + public float getInterpolation(float t) { + float tStep = 0.35f; + if (t < tStep) { + return t * (1f / tStep); + } else { + return 1f; + } + } + }; + + /** + * @param context everything needs a context :( + */ + public GlobalScreenshot(Context context) { + mContext = context; + mLayoutInflater = (LayoutInflater) + context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + // Inflate the screenshot layout + mDisplayMetrics = new DisplayMetrics(); + mDisplayMatrix = new Matrix(); + mScreenshotLayout = mLayoutInflater.inflate(R.layout.global_screenshot, null); + mBackgroundView = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot_background); + mScreenshotContainerView = (FrameLayout) mScreenshotLayout.findViewById(R.id.global_screenshot_container); + mScreenshotView = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot); + mScreenshotLayout.setFocusable(true); + mScreenshotLayout.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + // Intercept and ignore all touch events + return true; + } + }); + + // Setup the window that we are going to use + mIWindowManager = IWindowManager.Stub.asInterface( + ServiceManager.getService(Context.WINDOW_SERVICE)); + mWindowLayoutParams = new WindowManager.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0, 0, + WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY, + WindowManager.LayoutParams.FLAG_FULLSCREEN + | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED + | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED_SYSTEM + | WindowManager.LayoutParams.FLAG_KEEP_SURFACE_WHILE_ANIMATING + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED, + PixelFormat.TRANSLUCENT); + mWindowLayoutParams.token = new Binder(); + mWindowLayoutParams.setTitle("ScreenshotAnimation"); + mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + mDisplay = mWindowManager.getDefaultDisplay(); + } + + /** + * Creates a new worker thread and saves the screenshot to the media store. + */ + private void saveScreenshotInWorkerThread() { + SaveImageInBackgroundData data = new SaveImageInBackgroundData(); + data.context = mContext; + data.image = mScreenBitmap; + new SaveImageInBackgroundTask().execute(data); + } + + /** + * @return the current display rotation in degrees + */ + private float getDegreesForRotation(int value) { + switch (value) { + case Surface.ROTATION_90: + return 90f; + case Surface.ROTATION_180: + return 180f; + case Surface.ROTATION_270: + return 270f; + } + return 0f; + } + + /** + * Takes a screenshot of the current display and shows an animation. + */ + void takeScreenshot() { + // We need to orient the screenshot correctly (and the Surface api seems to take screenshots + // only in the natural orientation of the device :!) + mDisplay.getRealMetrics(mDisplayMetrics); + float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels}; + float degrees = getDegreesForRotation(mDisplay.getRotation()); + boolean requiresRotation = (degrees > 0); + if (requiresRotation) { + // Get the dimensions of the device in its native orientation + mDisplayMatrix.reset(); + mDisplayMatrix.preRotate(-degrees); + mDisplayMatrix.mapPoints(dims); + dims[0] = Math.abs(dims[0]); + dims[1] = Math.abs(dims[1]); + } + mScreenBitmap = Surface.screenshot((int) dims[0], (int) dims[1]); + if (requiresRotation) { + // Rotate the screenshot to the current orientation + Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels, + mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(ss); + c.translate(ss.getWidth() / 2, ss.getHeight() / 2); + c.rotate(360f - degrees); + c.translate(-dims[0] / 2, -dims[1] / 2); + c.drawBitmap(mScreenBitmap, 0, 0, null); + mScreenBitmap = ss; + } + + // If we couldn't take the screenshot, notify the user + if (mScreenBitmap == null) { + Toast.makeText(mContext, R.string.screenshot_failed_toast, + Toast.LENGTH_SHORT).show(); + return; + } + + // Start the post-screenshot animation + startAnimation(); + } + + + /** + * Starts the animation after taking the screenshot + */ + private void startAnimation() { + // Add the view for the animation + mScreenshotView.setImageBitmap(mScreenBitmap); + mScreenshotLayout.requestFocus(); + + // Setup the animation with the screenshot just taken + if (mScreenshotAnimation != null) { + mScreenshotAnimation.end(); + } + + mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); + ValueAnimator screenshotFadeInAnim = createScreenshotFadeInAnimation(); + ValueAnimator screenshotFadeOutAnim = createScreenshotFadeOutAnimation(); + mScreenshotAnimation = new AnimatorSet(); + mScreenshotAnimation.play(screenshotFadeInAnim).before(screenshotFadeOutAnim); + mScreenshotAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + // Save the screenshot once we have a bit of time now + saveScreenshotInWorkerThread(); + + mWindowManager.removeView(mScreenshotLayout); + } + }); + mScreenshotAnimation.start(); + } + private ValueAnimator createScreenshotFadeInAnimation() { + ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); + anim.setInterpolator(mCubicInterpolator); + anim.setDuration(SCREENSHOT_FADE_IN_DURATION); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mBackgroundView.setVisibility(View.VISIBLE); + mScreenshotContainerView.setVisibility(View.VISIBLE); + } + }); + anim.addUpdateListener(new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float t = ((Float) animation.getAnimatedValue()).floatValue(); + mBackgroundView.setAlpha(mBackgroundViewAlphaInterpolator.getInterpolation(t) * + BACKGROUND_ALPHA); + float scaleT = SCREENSHOT_SCALE + (1f - t) * SCREENSHOT_SCALE; + mScreenshotContainerView.setAlpha(t*t*t*t); + mScreenshotContainerView.setScaleX(scaleT); + mScreenshotContainerView.setScaleY(scaleT); + mScreenshotContainerView.setRotation(t * SCREENSHOT_ROTATION); + } + }); + return anim; + } + private ValueAnimator createScreenshotFadeOutAnimation() { + ValueAnimator anim = ValueAnimator.ofFloat(1f, 0f); + anim.setInterpolator(mCubicInterpolator); + anim.setStartDelay(SCREENSHOT_FADE_OUT_DELAY); + anim.setDuration(SCREENSHOT_FADE_OUT_DURATION); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mBackgroundView.setVisibility(View.GONE); + mScreenshotContainerView.setVisibility(View.GONE); + } + }); + anim.addUpdateListener(new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float t = ((Float) animation.getAnimatedValue()).floatValue(); + float scaleT = SCREENSHOT_MIN_SCALE + + t*(SCREENSHOT_SCALE - SCREENSHOT_MIN_SCALE); + mScreenshotContainerView.setAlpha(t); + mScreenshotContainerView.setScaleX(scaleT); + mScreenshotContainerView.setScaleY(scaleT); + mBackgroundView.setAlpha(t * t * BACKGROUND_ALPHA); + } + }); + return anim; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java new file mode 100644 index 000000000000..35eaedf1f941 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -0,0 +1,50 @@ +/* + * 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. + */ + +package com.android.systemui.screenshot; + +import android.app.Service; +import android.app.AlertDialog; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.hardware.usb.UsbAccessory; +import android.hardware.usb.UsbManager; +import android.os.Bundle; +import android.os.IBinder; +import android.util.Log; + +import com.android.internal.app.AlertActivity; +import com.android.internal.app.AlertController; + +import com.android.systemui.R; + +public class TakeScreenshotService extends Service { + private static final String TAG = "TakeScreenshotService"; + + private static GlobalScreenshot mScreenshot; + + @Override + public IBinder onBind(Intent intent) { + if (mScreenshot == null) { + mScreenshot = new GlobalScreenshot(this); + } + mScreenshot.takeScreenshot(); + return null; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index d8474db3c2ef..4c7b0dd2ca54 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -246,7 +246,7 @@ public class PhoneStatusBar extends StatusBar { mIntruderAlertView.setClickable(true); try { - boolean showNav = res.getBoolean(R.bool.config_showNavigationBar); + boolean showNav = res.getBoolean(com.android.internal.R.bool.config_showNavigationBar); if (showNav) { mNavigationBarView = (NavigationBarView) View.inflate(context, R.layout.navigation_bar, null); diff --git a/packages/VpnDialogs/Android.mk b/packages/VpnDialogs/Android.mk index 89f010aaebcb..ac84125f8f4e 100644 --- a/packages/VpnDialogs/Android.mk +++ b/packages/VpnDialogs/Android.mk @@ -20,6 +20,8 @@ include $(CLEAR_VARS) LOCAL_MODULE_TAGS := optional +LOCAL_CERTIFICATE := platform + LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_PACKAGE_NAME := VpnDialogs diff --git a/packages/VpnDialogs/AndroidManifest.xml b/packages/VpnDialogs/AndroidManifest.xml index 4e6784ce0884..c0b0a08067cf 100644 --- a/packages/VpnDialogs/AndroidManifest.xml +++ b/packages/VpnDialogs/AndroidManifest.xml @@ -1,5 +1,6 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.vpndialogs"> + package="com.android.vpndialogs" + android:sharedUserId="android.uid.system"> <application android:label="VpnDialogs"> <activity android:name=".ConfirmDialog" diff --git a/packages/VpnDialogs/res/values/strings.xml b/packages/VpnDialogs/res/values/strings.xml index 8186e2629173..df6d36bd2814 100644 --- a/packages/VpnDialogs/res/values/strings.xml +++ b/packages/VpnDialogs/res/values/strings.xml @@ -29,6 +29,7 @@ <string name="accept">I trust this application.</string> + <string name="legacy_title">VPN is connected</string> <string name="configure">Configure</string> <string name="disconnect">Disconnect</string> diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java index ba3f3448f066..c076ba0bfd2f 100644 --- a/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java +++ b/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java @@ -64,9 +64,6 @@ public class ManageDialog extends Activity implements Handler.Callback, mService = IConnectivityManager.Stub.asInterface( ServiceManager.getService(Context.CONNECTIVITY_SERVICE)); - PackageManager pm = getPackageManager(); - ApplicationInfo app = pm.getApplicationInfo(mConfig.packageName, 0); - View view = View.inflate(this, R.layout.manage, null); if (mConfig.sessionName != null) { ((TextView) view.findViewById(R.id.session)).setText(mConfig.sessionName); @@ -75,15 +72,29 @@ public class ManageDialog extends Activity implements Handler.Callback, mDataTransmitted = (TextView) view.findViewById(R.id.data_transmitted); mDataReceived = (TextView) view.findViewById(R.id.data_received); - mDialog = new AlertDialog.Builder(this) - .setIcon(app.loadIcon(pm)) - .setTitle(app.loadLabel(pm)) - .setView(view) - .setNeutralButton(R.string.disconnect, this) - .setNegativeButton(android.R.string.cancel, this) - .create(); + if (mConfig.packageName == null) { + // Legacy VPN does not have a package name. + mDialog = new AlertDialog.Builder(this) + .setIcon(android.R.drawable.ic_dialog_info) + .setTitle(R.string.legacy_title) + .setView(view) + .setNeutralButton(R.string.disconnect, this) + .setNegativeButton(android.R.string.cancel, this) + .create(); + } else { + PackageManager pm = getPackageManager(); + ApplicationInfo app = pm.getApplicationInfo(mConfig.packageName, 0); + + mDialog = new AlertDialog.Builder(this) + .setIcon(app.loadIcon(pm)) + .setTitle(app.loadLabel(pm)) + .setView(view) + .setNeutralButton(R.string.disconnect, this) + .setNegativeButton(android.R.string.cancel, this) + .create(); + } - if (mConfig.configureActivity != null) { + if (mConfig.configureIntent != null) { mDialog.setButton(DialogInterface.BUTTON_POSITIVE, getText(R.string.configure), this); } @@ -113,9 +124,7 @@ public class ManageDialog extends Activity implements Handler.Callback, public void onClick(DialogInterface dialog, int which) { try { if (which == AlertDialog.BUTTON_POSITIVE) { - Intent intent = new Intent(); - intent.setClassName(mConfig.packageName, mConfig.configureActivity); - startActivity(intent); + mConfig.configureIntent.send(); } else if (which == AlertDialog.BUTTON_NEUTRAL) { mService.prepareVpn(""); } diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index b52e7e1c42a2..ad6cebb60b78 100755 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -28,6 +28,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.ServiceConnection; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.res.CompatibilityInfo; @@ -372,6 +373,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { // What we do when the user long presses on home private int mLongPressOnHomeBehavior = -1; + // Screenshot trigger states + private boolean mVolumeDownTriggered; + private boolean mPowerDownTriggered; + ShortcutManager mShortcutManager; PowerManager.WakeLock mBroadcastWakeLock; @@ -2339,6 +2344,26 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } + private void takeScreenshot() { + mHandler.post(new Runnable() { + @Override + public void run() { + ComponentName cn = new ComponentName("com.android.systemui", + "com.android.systemui.screenshot.TakeScreenshotService"); + Intent intent = new Intent(); + intent.setComponent(cn); + ServiceConnection conn = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) {} + @Override + public void onServiceDisconnected(ComponentName name) {} + }; + mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE); + mContext.unbindService(conn); + } + }); + } + /** {@inheritDoc} */ @Override public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags, boolean isScreenOn) { @@ -2398,6 +2423,24 @@ public class PhoneWindowManager implements WindowManagerPolicy { // Handle special keys. switch (keyCode) { case KeyEvent.KEYCODE_VOLUME_DOWN: + if (down) { + // If the power key down was already triggered, take the screenshot + if (mPowerDownTriggered) { + // Dismiss the power-key longpress + mHandler.removeCallbacks(mPowerLongPress); + mPowerKeyHandled = true; + + // Take the screenshot + takeScreenshot(); + + // Prevent the event from being passed through to the current activity + result &= ~ACTION_PASS_TO_USER; + break; + } + mVolumeDownTriggered = true; + } else { + mVolumeDownTriggered = false; + } case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_MUTE: { if (down) { @@ -2478,6 +2521,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_POWER: { result &= ~ACTION_PASS_TO_USER; if (down) { + // If the volume down key has been triggered, then just take the screenshot + if (mVolumeDownTriggered) { + // Take the screenshot + takeScreenshot(); + mPowerKeyHandled = true; + + // Prevent the event from being passed through to the current activity + break; + } + mPowerDownTriggered = true; + + ITelephony telephonyService = getTelephonyService(); boolean hungUp = false; if (telephonyService != null) { @@ -2499,6 +2554,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } interceptPowerKeyDown(!isScreenOn || hungUp); } else { + mPowerDownTriggered = false; if (interceptPowerKeyUp(canceled)) { result = (result & ~ACTION_POKE_USER_ACTIVITY) | ACTION_GO_TO_SLEEP; } diff --git a/services/input/EventHub.cpp b/services/input/EventHub.cpp index 95b8a5723e2f..ca2540bfad20 100644 --- a/services/input/EventHub.cpp +++ b/services/input/EventHub.cpp @@ -212,8 +212,8 @@ status_t EventHub::getAbsoluteAxisInfo(int32_t deviceId, int axis, struct input_absinfo info; if(ioctl(device->fd, EVIOCGABS(axis), &info)) { - LOGW("Error reading absolute controller %d for device %s fd %d\n", - axis, device->identifier.name.string(), device->fd); + LOGW("Error reading absolute controller %d for device %s fd %d, errno=%d", + axis, device->identifier.name.string(), device->fd, errno); return -errno; } @@ -335,6 +335,33 @@ int32_t EventHub::getSwitchStateLocked(Device* device, int32_t sw) const { return AKEY_STATE_UNKNOWN; } +status_t EventHub::getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const { + if (axis >= 0 && axis <= ABS_MAX) { + AutoMutex _l(mLock); + + Device* device = getDeviceLocked(deviceId); + if (device != NULL) { + return getAbsoluteAxisValueLocked(device, axis, outValue); + } + } + *outValue = 0; + return -1; +} + +status_t EventHub::getAbsoluteAxisValueLocked(Device* device, int32_t axis, + int32_t* outValue) const { + struct input_absinfo info; + + if(ioctl(device->fd, EVIOCGABS(axis), &info)) { + LOGW("Error reading absolute controller %d for device %s fd %d, errno=%d", + axis, device->identifier.name.string(), device->fd, errno); + return -errno; + } + + *outValue = info.value; + return OK; +} + bool EventHub::markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const { AutoMutex _l(mLock); diff --git a/services/input/EventHub.h b/services/input/EventHub.h index 0a34e45a565c..695dfdfb25e6 100644 --- a/services/input/EventHub.h +++ b/services/input/EventHub.h @@ -186,6 +186,8 @@ public: virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const = 0; virtual int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const = 0; virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const = 0; + virtual status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis, + int32_t* outValue) const = 0; /* * Examine key input devices for specific framework keycode support @@ -237,6 +239,7 @@ public: virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const; virtual int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const; virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const; + virtual status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const; virtual bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const; @@ -305,6 +308,7 @@ private: int32_t getScanCodeStateLocked(Device* device, int32_t scanCode) const; int32_t getKeyCodeStateLocked(Device* device, int32_t keyCode) const; int32_t getSwitchStateLocked(Device* device, int32_t sw) const; + int32_t getAbsoluteAxisValueLocked(Device* device, int32_t axis, int32_t* outValue) const; bool markSupportedKeyCodesLocked(Device* device, size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const; diff --git a/services/input/InputDispatcher.cpp b/services/input/InputDispatcher.cpp index 85ce38ae2020..10b9083549f2 100644 --- a/services/input/InputDispatcher.cpp +++ b/services/input/InputDispatcher.cpp @@ -1239,8 +1239,9 @@ int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime, const InputWindow* newHoverWindow = NULL; bool isSplit = mTouchState.split; - bool switchedDevice = mTouchState.deviceId != entry->deviceId - || mTouchState.source != entry->source; + bool switchedDevice = mTouchState.deviceId >= 0 + && (mTouchState.deviceId != entry->deviceId + || mTouchState.source != entry->source); bool isHoverAction = (maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE || maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER || maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT); diff --git a/services/input/InputReader.cpp b/services/input/InputReader.cpp index 82c3af370ea8..79218a5581ca 100644 --- a/services/input/InputReader.cpp +++ b/services/input/InputReader.cpp @@ -5394,7 +5394,6 @@ void SingleTouchInputMapper::configureRawAxes() { MultiTouchInputMapper::MultiTouchInputMapper(InputDevice* device) : TouchInputMapper(device), mSlotCount(0), mUsingSlotsProtocol(false) { - clearState(); } MultiTouchInputMapper::~MultiTouchInputMapper() { @@ -5404,6 +5403,24 @@ void MultiTouchInputMapper::clearState() { mAccumulator.clearSlots(mSlotCount); mAccumulator.clearButtons(); mButtonState = 0; + + if (mUsingSlotsProtocol) { + // Query the driver for the current slot index and use it as the initial slot + // before we start reading events from the device. It is possible that the + // current slot index will not be the same as it was when the first event was + // written into the evdev buffer, which means the input mapper could start + // out of sync with the initial state of the events in the evdev buffer. + // In the extremely unlikely case that this happens, the data from + // two slots will be confused until the next ABS_MT_SLOT event is received. + // This can cause the touch point to "jump", but at least there will be + // no stuck touches. + status_t status = getEventHub()->getAbsoluteAxisValue(getDeviceId(), ABS_MT_SLOT, + &mAccumulator.currentSlot); + if (status) { + LOGW("Could not retrieve current multitouch slot index. status=%d", status); + mAccumulator.currentSlot = -1; + } + } } void MultiTouchInputMapper::reset() { @@ -5682,6 +5699,8 @@ void MultiTouchInputMapper::configureRawAxes() { } mAccumulator.allocateSlots(mSlotCount); + + clearState(); } diff --git a/services/input/tests/InputReader_test.cpp b/services/input/tests/InputReader_test.cpp index e349248b1c5a..d3c5ece7cbef 100644 --- a/services/input/tests/InputReader_test.cpp +++ b/services/input/tests/InputReader_test.cpp @@ -429,6 +429,7 @@ class FakeEventHub : public EventHubInterface { KeyedVector<int32_t, int32_t> keyCodeStates; KeyedVector<int32_t, int32_t> scanCodeStates; KeyedVector<int32_t, int32_t> switchStates; + KeyedVector<int32_t, int32_t> absoluteAxisValue; KeyedVector<int32_t, KeyInfo> keys; KeyedVector<int32_t, bool> leds; Vector<VirtualKeyDefinition> virtualKeys; @@ -514,6 +515,11 @@ public: device->switchStates.replaceValueFor(switchCode, state); } + void setAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t value) { + Device* device = getDevice(deviceId); + device->absoluteAxisValue.replaceValueFor(axis, value); + } + void addKey(int32_t deviceId, int32_t scanCode, int32_t keyCode, uint32_t flags) { Device* device = getDevice(deviceId); KeyInfo info; @@ -677,6 +683,20 @@ private: return AKEY_STATE_UNKNOWN; } + virtual status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis, + int32_t* outValue) const { + Device* device = getDevice(deviceId); + if (device) { + ssize_t index = device->absoluteAxisValue.indexOfKey(axis); + if (index >= 0) { + *outValue = device->absoluteAxisValue.valueAt(index); + return OK; + } + } + *outValue = 0; + return -1; + } + virtual bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const { bool result = false; diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index 8fb6274a7afb..663f4f47f56e 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -2533,7 +2533,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { private VpnCallback() { } - public synchronized void override(String[] dnsServers) { + public synchronized void override(List<String> dnsServers, List<String> searchDomains) { // TODO: override DNS servers and http proxy. } diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java index adc65708d5ef..1c150f8a65ea 100644 --- a/services/java/com/android/server/NetworkManagementService.java +++ b/services/java/com/android/server/NetworkManagementService.java @@ -16,10 +16,11 @@ package com.android.server; +import static android.Manifest.permission.MANAGE_NETWORK_POLICY; import static android.net.NetworkStats.IFACE_ALL; import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.UID_ALL; -import static android.Manifest.permission.MANAGE_NETWORK_POLICY; +import static android.provider.Settings.Secure.NETSTATS_ENABLED; import android.content.Context; import android.content.pm.PackageManager; @@ -35,6 +36,7 @@ import android.os.Binder; import android.os.INetworkManagementService; import android.os.SystemClock; import android.os.SystemProperties; +import android.provider.Settings; import android.util.Log; import android.util.Slog; import android.util.SparseBooleanArray; @@ -123,6 +125,8 @@ class NetworkManagementService extends INetworkManagementService.Stub { /** Set of UIDs with active reject rules. */ private SparseBooleanArray mUidRejectOnQuota = new SparseBooleanArray(); + private boolean mBandwidthControlEnabled; + /** * Constructs a new NetworkManagementService instance * @@ -161,6 +165,29 @@ class NetworkManagementService extends INetworkManagementService.Stub { return new NetworkManagementService(context, procRoot); } + public void systemReady() { + + // only enable bandwidth control when support exists, and requested by + // system setting. + // TODO: eventually migrate to be always enabled + final boolean hasKernelSupport = new File("/proc/net/xt_qtaguid/ctrl").exists(); + final boolean shouldEnable = + Settings.Secure.getInt(mContext.getContentResolver(), NETSTATS_ENABLED, 0) != 0; + + mBandwidthControlEnabled = false; + if (hasKernelSupport && shouldEnable) { + Slog.d(TAG, "enabling bandwidth control"); + try { + mConnector.doCommand("bandwidth enable"); + mBandwidthControlEnabled = true; + } catch (NativeDaemonConnectorException e) { + Slog.e(TAG, "problem enabling bandwidth controls", e); + } + } else { + Slog.d(TAG, "not enabling bandwidth control"); + } + } + public void registerObserver(INetworkManagementEventObserver obs) { Slog.d(TAG, "Registering observer"); mObservers.add(obs); @@ -919,7 +946,7 @@ class NetworkManagementService extends INetworkManagementService.Stub { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); - if (mProcStatsNetfilter.exists()) { + if (mBandwidthControlEnabled) { return getNetworkStatsDetailNetfilter(UID_ALL); } else { return getNetworkStatsDetailUidstat(UID_ALL); @@ -930,6 +957,10 @@ class NetworkManagementService extends INetworkManagementService.Stub { public void setInterfaceQuota(String iface, long quota) { mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); + // silently discard when control disabled + // TODO: eventually migrate to be always enabled + if (!mBandwidthControlEnabled) return; + synchronized (mInterfaceQuota) { if (mInterfaceQuota.contains(iface)) { // TODO: eventually consider throwing @@ -953,6 +984,10 @@ class NetworkManagementService extends INetworkManagementService.Stub { public void removeInterfaceQuota(String iface) { mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); + // silently discard when control disabled + // TODO: eventually migrate to be always enabled + if (!mBandwidthControlEnabled) return; + synchronized (mInterfaceQuota) { if (!mInterfaceQuota.contains(iface)) { // TODO: eventually consider throwing @@ -976,6 +1011,10 @@ class NetworkManagementService extends INetworkManagementService.Stub { public void setUidNetworkRules(int uid, boolean rejectOnQuotaInterfaces) { mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); + // silently discard when control disabled + // TODO: eventually migrate to be always enabled + if (!mBandwidthControlEnabled) return; + synchronized (mUidRejectOnQuota) { final boolean oldRejectOnQuota = mUidRejectOnQuota.get(uid, false); if (oldRejectOnQuota == rejectOnQuotaInterfaces) { @@ -1011,7 +1050,7 @@ class NetworkManagementService extends INetworkManagementService.Stub { android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); } - if (mProcStatsNetfilter.exists()) { + if (mBandwidthControlEnabled) { return getNetworkStatsDetailNetfilter(uid); } else { return getNetworkStatsDetailUidstat(uid); @@ -1151,12 +1190,6 @@ class NetworkManagementService extends INetworkManagementService.Stub { return getInterfaceThrottle(iface, false); } - @Override - public void setBandwidthControlEnabled(boolean enabled) { - mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); - mConnector.doCommand(String.format("bandwidth %s", (enabled ? "enable" : "disable"))); - } - /** * Split given line into {@link ArrayList}. */ diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index dbfd145bc65f..8c7e279b7132 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -523,6 +523,7 @@ class ServerThread extends Thread { // These are needed to propagate to the runnable below. final Context contextF = context; final BatteryService batteryF = battery; + final NetworkManagementService networkManagementF = networkManagement; final NetworkStatsService networkStatsF = networkStats; final NetworkPolicyManagerService networkPolicyF = networkPolicy; final ConnectivityService connectivityF = connectivity; @@ -550,6 +551,7 @@ class ServerThread extends Thread { startSystemUi(contextF); if (batteryF != null) batteryF.systemReady(); + if (networkManagementF != null) networkManagementF.systemReady(); if (networkStatsF != null) networkStatsF.systemReady(); if (networkPolicyF != null) networkPolicyF.systemReady(); if (connectivityF != null) connectivityF.systemReady(); diff --git a/services/java/com/android/server/connectivity/Vpn.java b/services/java/com/android/server/connectivity/Vpn.java index 54bddb258aaf..a8be916427f3 100644 --- a/services/java/com/android/server/connectivity/Vpn.java +++ b/services/java/com/android/server/connectivity/Vpn.java @@ -40,9 +40,9 @@ import com.android.internal.R; import com.android.internal.net.VpnConfig; import com.android.server.ConnectivityService.VpnCallback; -import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.Charsets; +import java.util.Arrays; /** * @hide @@ -77,11 +77,13 @@ public class Vpn extends INetworkManagementEventObserver.Stub { return mPackageName; } - // Check the permission of the caller. - PackageManager pm = mContext.getPackageManager(); - VpnConfig.enforceCallingPackage(pm.getNameForUid(Binder.getCallingUid())); + // Only system user can call this method. + if (Binder.getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("Unauthorized Caller"); + } // Check the permission of the given package. + PackageManager pm = mContext.getPackageManager(); if (packageName.isEmpty()) { packageName = null; } else if (pm.checkPermission(VPN, packageName) != PackageManager.PERMISSION_GRANTED) { @@ -104,6 +106,12 @@ public class Vpn extends INetworkManagementEventObserver.Stub { mContext.sendBroadcast(intent); } + // Stop legacy VPN if it has been started. + if (mLegacyVpnRunner != null) { + mLegacyVpnRunner.exit(); + mLegacyVpnRunner = null; + } + Log.i(TAG, "Switched from " + mPackageName + " to " + packageName); mPackageName = packageName; return mPackageName; @@ -132,7 +140,7 @@ public class Vpn extends INetworkManagementEventObserver.Stub { /** * Configure a TUN interface and return its file descriptor. * - * @param configuration The parameters to configure the interface. + * @param config The parameters to configure the interface. * @return The file descriptor of the interface. */ public synchronized ParcelFileDescriptor establish(VpnConfig config) { @@ -151,11 +159,25 @@ public class Vpn extends INetworkManagementEventObserver.Stub { return null; } - // Create and configure the interface. + // Load the label. + String label = app.loadLabel(pm).toString(); + + // Load the icon and convert it into a bitmap. + Drawable icon = app.loadIcon(pm); + Bitmap bitmap = null; + if (icon.getIntrinsicWidth() > 0 && icon.getIntrinsicHeight() > 0) { + int width = mContext.getResources().getDimensionPixelSize( + android.R.dimen.notification_large_icon_width); + int height = mContext.getResources().getDimensionPixelSize( + android.R.dimen.notification_large_icon_height); + icon.setBounds(0, 0, width, height); + bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + icon.draw(new Canvas(bitmap)); + } + + // Create the interface and abort if any of the following steps fails. ParcelFileDescriptor descriptor = ParcelFileDescriptor.adoptFd(jniCreateInterface(config.mtu)); - - // Abort if any of the following steps fails. try { String name = jniGetInterfaceName(descriptor.getFd()); if (jniSetAddresses(name, config.addresses) < 1) { @@ -177,12 +199,15 @@ public class Vpn extends INetworkManagementEventObserver.Stub { throw e; } - String dnsServers = (config.dnsServers == null) ? "" : config.dnsServers.trim(); - mCallback.override(dnsServers.isEmpty() ? null : dnsServers.split(" ")); + // Override DNS servers and search domains. + mCallback.override(config.dnsServers, config.searchDomains); + // Fill more values. config.packageName = mPackageName; config.interfaceName = mInterfaceName; - showNotification(pm, app, config); + + // Show the notification! + showNotification(config, label, bitmap); return descriptor; } @@ -202,41 +227,26 @@ public class Vpn extends INetworkManagementEventObserver.Stub { public synchronized void interfaceRemoved(String name) { if (name.equals(mInterfaceName) && jniCheckInterface(name) == 0) { hideNotification(); - mInterfaceName = null; mCallback.restore(); + mInterfaceName = null; } } - private void showNotification(PackageManager pm, ApplicationInfo app, VpnConfig config) { + private void showNotification(VpnConfig config, String label, Bitmap icon) { NotificationManager nm = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); if (nm != null) { - // Load the icon and convert it into a bitmap. - Drawable icon = app.loadIcon(pm); - Bitmap bitmap = null; - if (icon.getIntrinsicWidth() > 0 && icon.getIntrinsicHeight() > 0) { - int width = mContext.getResources().getDimensionPixelSize( - android.R.dimen.notification_large_icon_width); - int height = mContext.getResources().getDimensionPixelSize( - android.R.dimen.notification_large_icon_height); - icon.setBounds(0, 0, width, height); - bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - icon.draw(new Canvas(bitmap)); - } - - // Load the label. - String label = app.loadLabel(pm).toString(); - - // Build the notification. + String title = (label == null) ? mContext.getString(R.string.vpn_title) : + mContext.getString(R.string.vpn_title_long, label); String text = (config.sessionName == null) ? mContext.getString(R.string.vpn_text) : mContext.getString(R.string.vpn_text_long, config.sessionName); + long identity = Binder.clearCallingIdentity(); Notification notification = new Notification.Builder(mContext) .setSmallIcon(R.drawable.vpn_connected) - .setLargeIcon(bitmap) - .setTicker(mContext.getString(R.string.vpn_ticker, label)) - .setContentTitle(mContext.getString(R.string.vpn_title, label)) + .setLargeIcon(icon) + .setContentTitle(title) .setContentText(text) .setContentIntent(VpnConfig.getIntentForNotification(mContext, config)) .setDefaults(Notification.DEFAULT_ALL) @@ -267,27 +277,22 @@ public class Vpn extends INetworkManagementEventObserver.Stub { private native void jniProtectSocket(int fd, String name); /** - * Handle legacy VPN requests. This method stops the services and restart + * Handle legacy VPN requests. This method stops the daemons and restart * them if their arguments are not null. Heavy things are offloaded to * another thread, so callers will not be blocked too long. * * @param raoocn The arguments to be passed to racoon. * @param mtpd The arguments to be passed to mtpd. */ - public synchronized void doLegacyVpn(String[] racoon, String[] mtpd) { - // Currently only system user is allowed. - if (Binder.getCallingUid() != Process.SYSTEM_UID) { - throw new SecurityException("Unauthorized Caller"); - } + public synchronized void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd) { + // Stop the current VPN just like a normal VPN application. + prepare(""); - // If the previous runner is still alive, interrupt it. - if (mLegacyVpnRunner != null && mLegacyVpnRunner.isAlive()) { - mLegacyVpnRunner.interrupt(); - } + // Legacy VPN does not have a package name. + config.packageName = null; // Start a new runner and we are done! - mLegacyVpnRunner = new LegacyVpnRunner( - new String[] {"racoon", "mtpd"}, racoon, mtpd); + mLegacyVpnRunner = new LegacyVpnRunner(config, racoon, mtpd); mLegacyVpnRunner.start(); } @@ -300,17 +305,25 @@ public class Vpn extends INetworkManagementEventObserver.Stub { */ private class LegacyVpnRunner extends Thread { private static final String TAG = "LegacyVpnRunner"; - private static final String NONE = "--"; - private final String[] mServices; + private final VpnConfig mConfig; + private final String[] mDaemons; private final String[][] mArguments; private long mTimer = -1; - public LegacyVpnRunner(String[] services, String[]... arguments) { + public LegacyVpnRunner(VpnConfig config, String[] racoon, String[] mtpd) { super(TAG); - mServices = services; - mArguments = arguments; + mConfig = config; + mDaemons = new String[] {"racoon", "mtpd"}; + mArguments = new String[][] {racoon, mtpd}; + } + + public void exit() { + for (String daemon : mDaemons) { + SystemProperties.set("ctl.stop", daemon); + } + interrupt(); } @Override @@ -318,9 +331,9 @@ public class Vpn extends INetworkManagementEventObserver.Stub { // Wait for the previous thread since it has been interrupted. Log.v(TAG, "wait"); synchronized (TAG) { - Log.v(TAG, "run"); + Log.v(TAG, "begin"); execute(); - Log.v(TAG, "exit"); + Log.v(TAG, "end"); } } @@ -342,14 +355,14 @@ public class Vpn extends INetworkManagementEventObserver.Stub { // Initialize the timer. checkpoint(false); - // First stop the services. - for (String service : mServices) { - SystemProperties.set("ctl.stop", service); + // First stop the daemons. + for (String daemon : mDaemons) { + SystemProperties.set("ctl.stop", daemon); } - // Wait for the services to stop. - for (String service : mServices) { - String key = "init.svc." + service; + // Wait for the daemons to stop. + for (String daemon : mDaemons) { + String key = "init.svc." + daemon; while (!"stopped".equals(SystemProperties.get(key))) { checkpoint(true); } @@ -363,7 +376,7 @@ public class Vpn extends INetworkManagementEventObserver.Stub { checkpoint(true); } - // Check if we need to restart some services. + // Check if we need to restart some daemons. boolean restart = false; for (String[] arguments : mArguments) { restart = restart || (arguments != null); @@ -372,19 +385,19 @@ public class Vpn extends INetworkManagementEventObserver.Stub { return; } - // Start the service with arguments. - for (int i = 0; i < mServices.length; ++i) { + // Start the daemon with arguments. + for (int i = 0; i < mDaemons.length; ++i) { String[] arguments = mArguments[i]; if (arguments == null) { continue; } - // Start the service. - String service = mServices[i]; - SystemProperties.set("ctl.start", service); + // Start the daemon. + String daemon = mDaemons[i]; + SystemProperties.set("ctl.start", daemon); - // Wait for the service to start. - String key = "init.svc." + service; + // Wait for the daemon to start. + String key = "init.svc." + daemon; while (!"running".equals(SystemProperties.get(key))) { checkpoint(true); } @@ -392,7 +405,7 @@ public class Vpn extends INetworkManagementEventObserver.Stub { // Create the control socket. LocalSocket socket = new LocalSocket(); LocalSocketAddress address = new LocalSocketAddress( - service, LocalSocketAddress.Namespace.RESERVED); + daemon, LocalSocketAddress.Namespace.RESERVED); // Wait for the socket to connect. while (true) { @@ -407,22 +420,22 @@ public class Vpn extends INetworkManagementEventObserver.Stub { socket.setSoTimeout(500); // Send over the arguments. - OutputStream output = socket.getOutputStream(); + OutputStream out = socket.getOutputStream(); for (String argument : arguments) { byte[] bytes = argument.getBytes(Charsets.UTF_8); if (bytes.length >= 0xFFFF) { throw new IllegalArgumentException("argument too large"); } - output.write(bytes.length >> 8); - output.write(bytes.length); - output.write(bytes); + out.write(bytes.length >> 8); + out.write(bytes.length); + out.write(bytes); checkpoint(false); } // Send End-Of-Arguments. - output.write(0xFF); - output.write(0xFF); - output.flush(); + out.write(0xFF); + out.write(0xFF); + out.flush(); socket.close(); } @@ -433,25 +446,47 @@ public class Vpn extends INetworkManagementEventObserver.Stub { while (NONE.equals(SystemProperties.get("vpn.dns")) || NONE.equals(SystemProperties.get("vpn.via"))) { - // Check if a running service is dead. - for (int i = 0; i < mServices.length; ++i) { - String service = mServices[i]; + // Check if a running daemon is dead. + for (int i = 0; i < mDaemons.length; ++i) { + String daemon = mDaemons[i]; if (mArguments[i] != null && !"running".equals( - SystemProperties.get("init.svc." + service))) { - throw new IllegalArgumentException(service + " is dead"); + SystemProperties.get("init.svc." + daemon))) { + throw new IllegalArgumentException(daemon + " is dead"); } } checkpoint(true); } - // Great! Now we are connected! - Log.i(TAG, "connected!"); - // TODO: + // Now we are connected. Get the interface. + mConfig.interfaceName = SystemProperties.get("vpn.via"); + + // Get the DNS servers if they are not set in config. + if (mConfig.dnsServers == null || mConfig.dnsServers.size() == 0) { + String dnsServers = SystemProperties.get("vpn.dns").trim(); + if (!dnsServers.isEmpty()) { + mConfig.dnsServers = Arrays.asList(dnsServers.split(" ")); + } + } + + // TODO: support routes and search domains for IPSec Mode-CFG. + + // This is it! Here is the end of our journey! + synchronized (Vpn.this) { + // Check if the thread is interrupted while we are waiting. + checkpoint(false); + if (mConfig.routes != null) { + jniSetRoutes(mConfig.interfaceName, mConfig.routes); + } + mInterfaceName = mConfig.interfaceName; + mCallback.override(mConfig.dnsServers, mConfig.searchDomains); + showNotification(mConfig, null, null); + } + Log.i(TAG, "Connected!"); } catch (Exception e) { - Log.i(TAG, e.getMessage()); - for (String service : mServices) { - SystemProperties.set("ctl.stop", service); + Log.i(TAG, "Abort due to " + e.getMessage()); + for (String daemon : mDaemons) { + SystemProperties.set("ctl.stop", daemon); } } } diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java index 1f2ec2ca8279..2a17cbe7afda 100644 --- a/services/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java @@ -1044,8 +1044,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // TODO: only dispatch when rules actually change - // record rule locally to dispatch to new listeners - mUidRules.put(uid, uidRules); + if (uidRules == RULE_ALLOW_ALL) { + mUidRules.delete(uid); + } else { + mUidRules.put(uid, uidRules); + } final boolean rejectMetered = (uidRules & RULE_REJECT_METERED) != 0; setUidNetworkRules(uid, rejectMetered); diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java index 7610a1181d60..b4bd17690605 100644 --- a/services/java/com/android/server/net/NetworkStatsService.java +++ b/services/java/com/android/server/net/NetworkStatsService.java @@ -134,7 +134,6 @@ public class NetworkStatsService extends INetworkStatsService.Stub { * Settings that can be changed externally. */ public interface NetworkStatsSettings { - public boolean getEnabled(); public long getPollInterval(); public long getPersistThreshold(); public long getNetworkBucketDuration(); @@ -207,20 +206,6 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } public void systemReady() { - if (mSettings.getEnabled()) { - try { - // enable low-level bandwidth stats and control - // TODO: consider shipping with this enabled by default - mNetworkManager.setBandwidthControlEnabled(true); - } catch (RemoteException e) { - Slog.e(TAG, "problem talking to netd while enabling bandwidth controls", e); - } catch (NativeDaemonConnectorException ndce) { - Slog.e(TAG, "problem enabling bandwidth controls", ndce); - } - } else { - Slog.w(TAG, "detailed network stats disabled"); - } - synchronized (mStatsLock) { // read historical network stats from disk, since policy service // might need them right away. we delay loading detailed UID stats @@ -389,6 +374,15 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } } + @Override + public void forceUpdate() { + mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG); + + synchronized (mStatsLock) { + performPollLocked(true, false); + } + } + /** * Receiver that watches for {@link IConnectivityManager} to claim network * interfaces. Used to associate {@link TelephonyManager#getSubscriberId()} @@ -905,6 +899,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub { argSet.add(arg); } + final boolean fullHistory = argSet.contains("full"); + synchronized (mStatsLock) { // TODO: remove this testing code, since it corrupts stats if (argSet.contains("generate")) { @@ -930,7 +926,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { for (NetworkIdentitySet ident : mNetworkStats.keySet()) { final NetworkStatsHistory history = mNetworkStats.get(ident); pw.print(" ident="); pw.println(ident.toString()); - history.dump(" ", pw); + history.dump(" ", pw, fullHistory); } if (argSet.contains("detail")) { @@ -950,7 +946,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { final NetworkStatsHistory history = uidStats.valueAt(i); pw.print(" UID="); pw.print(uid); pw.print(" tag="); pw.println(tag); - history.dump(" ", pw); + history.dump(" ", pw, fullHistory); } } } @@ -1058,15 +1054,6 @@ public class NetworkStatsService extends INetworkStatsService.Stub { return Settings.Secure.getLong(mResolver, name, def); } - public boolean getEnabled() { - if (!new File("/proc/net/xt_qtaguid/ctrl").exists()) { - Slog.w(TAG, "kernel does not support bandwidth control"); - return false; - } - // TODO: once things stabilize, enable by default. - // For now: ./vendor/google/tools/override-gservices secure:netstats_enabled=1 - return Settings.Secure.getInt(mResolver, NETSTATS_ENABLED, 0) != 0; - } public long getPollInterval() { return getSecureLong(NETSTATS_POLL_INTERVAL, 15 * MINUTE_IN_MILLIS); } diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java index 22e2ddeb78a8..ea5d26b99056 100644 --- a/services/java/com/android/server/pm/PackageManagerService.java +++ b/services/java/com/android/server/pm/PackageManagerService.java @@ -157,7 +157,6 @@ public class PackageManagerService extends IPackageManager.Stub { private static final int RADIO_UID = Process.PHONE_UID; private static final int LOG_UID = Process.LOG_UID; private static final int NFC_UID = Process.NFC_UID; - private static final int KEYCHAIN_UID = Process.KEYCHAIN_UID; static final int FIRST_APPLICATION_UID = Process.FIRST_APPLICATION_UID; static final int MAX_APPLICATION_UIDS = 1000; @@ -761,10 +760,6 @@ public class PackageManagerService extends IPackageManager.Stub { MULTIPLE_APPLICATION_UIDS ? NFC_UID : FIRST_APPLICATION_UID, ApplicationInfo.FLAG_SYSTEM); - mSettings.addSharedUserLPw("android.uid.keychain", - MULTIPLE_APPLICATION_UIDS - ? KEYCHAIN_UID : FIRST_APPLICATION_UID, - ApplicationInfo.FLAG_SYSTEM); String separateProcesses = SystemProperties.get("debug.separate_processes"); if (separateProcesses != null && separateProcesses.length() > 0) { diff --git a/services/java/com/android/server/usb/UsbDeviceManager.java b/services/java/com/android/server/usb/UsbDeviceManager.java index 918f1b6b8355..13a76b379264 100644 --- a/services/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/java/com/android/server/usb/UsbDeviceManager.java @@ -45,7 +45,6 @@ import android.os.storage.StorageVolume; import android.os.SystemProperties; import android.os.UEventObserver; import android.provider.Settings; -import android.util.Log; import android.util.Slog; import java.io.File; @@ -62,7 +61,7 @@ import java.util.List; public class UsbDeviceManager { private static final String TAG = UsbDeviceManager.class.getSimpleName(); - private static final boolean LOG = false; + private static final boolean DEBUG = false; private static final String USB_STATE_MATCH = "DEVPATH=/devices/virtual/android_usb/android0"; @@ -93,18 +92,9 @@ public class UsbDeviceManager { private final UsbSettingsManager mSettingsManager; private NotificationManager mNotificationManager; private final boolean mHasUsbAccessory; - - // for USB connected notification - private boolean mUsbNotificationShown; private boolean mUseUsbNotification; - private Notification mUsbNotification; - - // for adb connected notification - private boolean mAdbNotificationShown; - private Notification mAdbNotification; private boolean mAdbEnabled; - private class AdbSettingsObserver extends ContentObserver { public AdbSettingsObserver() { super(null); @@ -117,115 +107,20 @@ public class UsbDeviceManager { } } - private void updateUsbNotification(boolean connected) { - if (mNotificationManager == null || !mUseUsbNotification) return; - if (connected) { - if (!mUsbNotificationShown) { - Resources r = mContext.getResources(); - CharSequence title = r.getText( - com.android.internal.R.string.usb_preferences_notification_title); - CharSequence message = r.getText( - com.android.internal.R.string.usb_preferece_notification_message); - - if (mUsbNotification == null) { - mUsbNotification = new Notification(); - mUsbNotification.icon = com.android.internal.R.drawable.stat_sys_data_usb; - mUsbNotification.when = 0; - mUsbNotification.flags = Notification.FLAG_ONGOING_EVENT; - mUsbNotification.tickerText = title; - mUsbNotification.defaults = 0; // please be quiet - mUsbNotification.sound = null; - mUsbNotification.vibrate = null; - } - - Intent intent = new Intent(); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | - Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - intent.setClassName("com.android.systemui", - "com.android.systemui.usb.UsbPreferenceActivity"); - PendingIntent pi = PendingIntent.getActivity(mContext, 0, - intent, 0); - - mUsbNotification.setLatestEventInfo(mContext, title, message, pi); - - mUsbNotificationShown = true; - mNotificationManager.notify( - com.android.internal.R.string.usb_preferences_notification_title, - mUsbNotification); - } - - } else if (mUsbNotificationShown) { - mUsbNotificationShown = false; - mNotificationManager.cancel( - com.android.internal.R.string.usb_preferences_notification_title); - } - } - - private void updateAdbNotification(boolean adbEnabled) { - if (mNotificationManager == null) return; - if (adbEnabled) { - if ("0".equals(SystemProperties.get("persist.adb.notify"))) return; - - if (!mAdbNotificationShown) { - Resources r = mContext.getResources(); - CharSequence title = r.getText( - com.android.internal.R.string.adb_active_notification_title); - CharSequence message = r.getText( - com.android.internal.R.string.adb_active_notification_message); - - if (mAdbNotification == null) { - mAdbNotification = new Notification(); - mAdbNotification.icon = com.android.internal.R.drawable.stat_sys_adb; - mAdbNotification.when = 0; - mAdbNotification.flags = Notification.FLAG_ONGOING_EVENT; - mAdbNotification.tickerText = title; - mAdbNotification.defaults = 0; // please be quiet - mAdbNotification.sound = null; - mAdbNotification.vibrate = null; - } - - Intent intent = new Intent( - Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | - Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - // Note: we are hard-coding the component because this is - // an important security UI that we don't want anyone - // intercepting. - intent.setComponent(new ComponentName("com.android.settings", - "com.android.settings.DevelopmentSettings")); - PendingIntent pi = PendingIntent.getActivity(mContext, 0, - intent, 0); - - mAdbNotification.setLatestEventInfo(mContext, title, message, pi); - - mAdbNotificationShown = true; - mNotificationManager.notify( - com.android.internal.R.string.adb_active_notification_title, - mAdbNotification); - } - } else if (mAdbNotificationShown) { - mAdbNotificationShown = false; - mNotificationManager.cancel( - com.android.internal.R.string.adb_active_notification_title); - } - } - /* * Listens for uevent messages from the kernel to monitor the USB state */ private final UEventObserver mUEventObserver = new UEventObserver() { @Override public void onUEvent(UEventObserver.UEvent event) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Slog.v(TAG, "USB UEVENT: " + event.toString()); - } + if (DEBUG) Slog.v(TAG, "USB UEVENT: " + event.toString()); String state = event.get("USB_STATE"); String accessory = event.get("ACCESSORY"); if (state != null) { mHandler.updateState(state); } else if ("START".equals(accessory)) { - Slog.d(TAG, "got accessory start"); + if (DEBUG) Slog.d(TAG, "got accessory start"); setCurrentFunction(UsbManager.USB_FUNCTION_ACCESSORY, false); } } @@ -319,16 +214,32 @@ public class UsbDeviceManager { private String mDefaultFunctions; private UsbAccessory mCurrentAccessory; private boolean mDeferAccessoryAttached; + private int mUsbNotificationId; + private boolean mAdbNotificationShown; + + private static final int NOTIFICATION_NONE = 0; + private static final int NOTIFICATION_MTP = 1; + private static final int NOTIFICATION_PTP = 2; + private static final int NOTIFICATION_INSTALLER = 3; + private static final int NOTIFICATION_ADB = 4; public UsbHandler() { - // Read initial USB state try { + // sanity check the sys.usb.config system property + // this may be necessary if we crashed while switching USB configurations + String config = SystemProperties.get("sys.usb.config", "none"); + if (config.equals("none")) { + String persistConfig = SystemProperties.get("persist.sys.usb.config", "none"); + Slog.w(TAG, "resetting config to persistent property: " + persistConfig); + SystemProperties.set("sys.usb.config", persistConfig); + } + + // Read initial USB state mCurrentFunctions = FileUtils.readTextFile( new File(FUNCTIONS_PATH), 0, null).trim(); mDefaultFunctions = mCurrentFunctions; String state = FileUtils.readTextFile(new File(STATE_PATH), 0, null).trim(); updateState(state); - mAdbEnabled = containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_ADB); // Upgrade step for previous versions that used persist.service.adb.enable @@ -414,12 +325,12 @@ public class UsbDeviceManager { } catch (InterruptedException e) { } } - Log.e(TAG, "waitForState(" + state + ") FAILED"); + Slog.e(TAG, "waitForState(" + state + ") FAILED"); return false; } private boolean setUsbConfig(String config) { - Log.d(TAG, "setUsbConfig(" + config + ")"); + if (DEBUG) Slog.d(TAG, "setUsbConfig(" + config + ")"); // set the new configuration SystemProperties.set("sys.usb.config", config); return waitForState(config); @@ -428,7 +339,7 @@ public class UsbDeviceManager { private void doSetCurrentFunctions(String functions) { if (!mCurrentFunctions.equals(functions)) { if (!setUsbConfig("none") || !setUsbConfig(functions)) { - Log.e(TAG, "Failed to switch USB configuration to " + functions); + Slog.e(TAG, "Failed to switch USB configuration to " + functions); // revert to previous configuration if we fail setUsbConfig(mCurrentFunctions); } else { @@ -438,6 +349,7 @@ public class UsbDeviceManager { } private void setAdbEnabled(boolean enable) { + if (DEBUG) Slog.d(TAG, "setAdbEnabled: " + enable); if (enable != mAdbEnabled) { mAdbEnabled = enable; String functions; @@ -449,7 +361,7 @@ public class UsbDeviceManager { functions = removeFunction(mDefaultFunctions, UsbManager.USB_FUNCTION_ADB); } setCurrentFunction(functions, true); - updateAdbNotification(mAdbEnabled && mConnected); + updateAdbNotification(); } } @@ -469,7 +381,7 @@ public class UsbDeviceManager { String[] strings = nativeGetAccessoryStrings(); if (strings != null) { mCurrentAccessory = new UsbAccessory(strings); - Log.d(TAG, "entering USB accessory mode: " + mCurrentAccessory); + Slog.d(TAG, "entering USB accessory mode: " + mCurrentAccessory); // defer accessoryAttached if system is not ready if (mSystemReady) { mSettingsManager.accessoryAttached(mCurrentAccessory); @@ -477,12 +389,12 @@ public class UsbDeviceManager { mDeferAccessoryAttached = true; } } else { - Log.e(TAG, "nativeGetAccessoryStrings failed"); + Slog.e(TAG, "nativeGetAccessoryStrings failed"); } } else if (!mConnected) { // make sure accessory mode is off // and restore default functions - Log.d(TAG, "exited USB accessory mode"); + Slog.d(TAG, "exited USB accessory mode"); setEnabledFunctions(mDefaultFunctions); if (mCurrentAccessory != null) { @@ -517,8 +429,8 @@ public class UsbDeviceManager { case MSG_UPDATE_STATE: mConnected = (msg.arg1 == 1); mConfigured = (msg.arg2 == 1); - updateUsbNotification(mConnected); - updateAdbNotification(mAdbEnabled && mConnected); + updateUsbNotification(); + updateAdbNotification(); if (containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_ACCESSORY)) { updateCurrentAccessory(); @@ -562,8 +474,8 @@ public class UsbDeviceManager { } break; case MSG_SYSTEM_READY: - updateUsbNotification(mConnected); - updateAdbNotification(mAdbEnabled && mConnected); + updateUsbNotification(); + updateAdbNotification(); updateUsbState(); if (mCurrentAccessory != null && mDeferAccessoryAttached) { mSettingsManager.accessoryAttached(mCurrentAccessory); @@ -576,6 +488,105 @@ public class UsbDeviceManager { return mCurrentAccessory; } + private void updateUsbNotification() { + if (mNotificationManager == null || !mUseUsbNotification) return; + if (mConnected) { + Resources r = mContext.getResources(); + CharSequence title = null; + int id = NOTIFICATION_NONE; + if (containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_MTP)) { + title = r.getText( + com.android.internal.R.string.usb_mtp_notification_title); + id = NOTIFICATION_MTP; + } else if (containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_PTP)) { + title = r.getText( + com.android.internal.R.string.usb_ptp_notification_title); + id = NOTIFICATION_PTP; + } else if (containsFunction(mCurrentFunctions, + UsbManager.USB_FUNCTION_MASS_STORAGE)) { + title = r.getText( + com.android.internal.R.string.usb_cd_installer_notification_title); + id = NOTIFICATION_INSTALLER; + } else { + Slog.e(TAG, "No known USB function in updateUsbNotification"); + } + if (id != mUsbNotificationId) { + // clear notification if title needs changing + if (mUsbNotificationId != NOTIFICATION_NONE) { + mNotificationManager.cancel(mUsbNotificationId); + mUsbNotificationId = NOTIFICATION_NONE; + } + } + if (mUsbNotificationId == NOTIFICATION_NONE) { + CharSequence message = r.getText( + com.android.internal.R.string.usb_notification_message); + + Notification notification = new Notification(); + notification.icon = com.android.internal.R.drawable.stat_sys_data_usb; + notification.when = 0; + notification.flags = Notification.FLAG_ONGOING_EVENT; + notification.tickerText = title; + notification.defaults = 0; // please be quiet + notification.sound = null; + notification.vibrate = null; + + Intent intent = new Intent(); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | + Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + intent.setClassName("com.android.systemui", + "com.android.systemui.usb.UsbPreferenceActivity"); + PendingIntent pi = PendingIntent.getActivity(mContext, 0, + intent, 0); + notification.setLatestEventInfo(mContext, title, message, pi); + mNotificationManager.notify(id, notification); + mUsbNotificationId = id; + } + + } else if (mUsbNotificationId != NOTIFICATION_NONE) { + mNotificationManager.cancel(mUsbNotificationId); + mUsbNotificationId = NOTIFICATION_NONE; + } + } + + private void updateAdbNotification() { + if (mNotificationManager == null) return; + if (mAdbEnabled && mConnected) { + if ("0".equals(SystemProperties.get("persist.adb.notify"))) return; + + if (!mAdbNotificationShown) { + Resources r = mContext.getResources(); + CharSequence title = r.getText( + com.android.internal.R.string.adb_active_notification_title); + CharSequence message = r.getText( + com.android.internal.R.string.adb_active_notification_message); + + Notification notification = new Notification(); + notification.icon = com.android.internal.R.drawable.stat_sys_adb; + notification.when = 0; + notification.flags = Notification.FLAG_ONGOING_EVENT; + notification.tickerText = title; + notification.defaults = 0; // please be quiet + notification.sound = null; + notification.vibrate = null; + + Intent intent = new Intent( + Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | + Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + intent.setComponent(new ComponentName("com.android.settings", + "com.android.settings.DevelopmentSettings")); + PendingIntent pi = PendingIntent.getActivity(mContext, 0, + intent, 0); + notification.setLatestEventInfo(mContext, title, message, pi); + mAdbNotificationShown = true; + mNotificationManager.notify(NOTIFICATION_ADB, notification); + } + } else if (mAdbNotificationShown) { + mAdbNotificationShown = false; + mNotificationManager.cancel(NOTIFICATION_ADB); + } + } + public void dump(FileDescriptor fd, PrintWriter pw) { pw.println(" USB Device State:"); pw.println(" Current Functions: " + mCurrentFunctions); @@ -608,6 +619,7 @@ public class UsbDeviceManager { } public void setCurrentFunction(String function, boolean makeDefault) { + if (DEBUG) Slog.d(TAG, "setCurrentFunction(" + function + ") default: " + makeDefault); mHandler.sendMessage(MSG_SET_CURRENT_FUNCTION, function, makeDefault); } diff --git a/services/java/com/android/server/usb/UsbHostManager.java b/services/java/com/android/server/usb/UsbHostManager.java index 923b0494f597..0a0ff598d7d8 100644 --- a/services/java/com/android/server/usb/UsbHostManager.java +++ b/services/java/com/android/server/usb/UsbHostManager.java @@ -37,7 +37,6 @@ import android.os.Parcelable; import android.os.ParcelFileDescriptor; import android.os.UEventObserver; import android.provider.Settings; -import android.util.Log; import android.util.Slog; import java.io.File; @@ -112,7 +111,7 @@ public class UsbHostManager { synchronized (mLock) { if (mDevices.get(deviceName) != null) { - Log.w(TAG, "device already on mDevices list: " + deviceName); + Slog.w(TAG, "device already on mDevices list: " + deviceName); return; } @@ -148,7 +147,7 @@ public class UsbHostManager { } catch (Exception e) { // beware of index out of bound exceptions, which might happen if // a device does not set bNumEndpoints correctly - Log.e(TAG, "error parsing USB descriptors", e); + Slog.e(TAG, "error parsing USB descriptors", e); return; } diff --git a/services/java/com/android/server/usb/UsbSettingsManager.java b/services/java/com/android/server/usb/UsbSettingsManager.java index 911367711faf..0baafbb2bf72 100644 --- a/services/java/com/android/server/usb/UsbSettingsManager.java +++ b/services/java/com/android/server/usb/UsbSettingsManager.java @@ -35,7 +35,7 @@ import android.hardware.usb.UsbManager; import android.os.Binder; import android.os.FileUtils; import android.os.Process; -import android.util.Log; +import android.util.Slog; import android.util.SparseBooleanArray; import android.util.Xml; @@ -62,6 +62,7 @@ import java.util.List; class UsbSettingsManager { private static final String TAG = "UsbSettingsManager"; + private static final boolean DEBUG = false; private static final File sSettingsFile = new File("/data/system/usb_device_manager.xml"); private final Context mContext; @@ -410,9 +411,9 @@ class UsbSettingsManager { } } } catch (FileNotFoundException e) { - Log.w(TAG, "settings file not found"); + if (DEBUG) Slog.d(TAG, "settings file not found"); } catch (Exception e) { - Log.e(TAG, "error reading settings file, deleting to start fresh", e); + Slog.e(TAG, "error reading settings file, deleting to start fresh", e); sSettingsFile.delete(); } finally { if (stream != null) { @@ -428,7 +429,7 @@ class UsbSettingsManager { FileOutputStream fos = null; try { FileOutputStream fstr = new FileOutputStream(sSettingsFile); - Log.d(TAG, "writing settings to " + fstr); + if (DEBUG) Slog.d(TAG, "writing settings to " + fstr); BufferedOutputStream str = new BufferedOutputStream(fstr); FastXmlSerializer serializer = new FastXmlSerializer(); serializer.setOutput(str, "utf-8"); @@ -457,7 +458,7 @@ class UsbSettingsManager { FileUtils.sync(fstr); str.close(); } catch (Exception e) { - Log.e(TAG, "error writing settings file, deleting to start fresh", e); + Slog.e(TAG, "error writing settings file, deleting to start fresh", e); sSettingsFile.delete(); } } @@ -472,7 +473,7 @@ class UsbSettingsManager { try { parser = ai.loadXmlMetaData(mPackageManager, metaDataName); if (parser == null) { - Log.w(TAG, "no meta-data for " + info); + Slog.w(TAG, "no meta-data for " + info); return false; } @@ -494,7 +495,7 @@ class UsbSettingsManager { XmlUtils.nextElement(parser); } } catch (Exception e) { - Log.w(TAG, "Unable to load component info " + info.toString(), e); + Slog.w(TAG, "Unable to load component info " + info.toString(), e); } finally { if (parser != null) parser.close(); } @@ -553,7 +554,7 @@ class UsbSettingsManager { Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_DETACHED); intent.putExtra(UsbManager.EXTRA_DEVICE, device); - Log.d(TAG, "usbDeviceRemoved, sending " + intent); + if (DEBUG) Slog.d(TAG, "usbDeviceRemoved, sending " + intent); mContext.sendBroadcast(intent); } @@ -604,7 +605,7 @@ class UsbSettingsManager { try { mContext.startActivity(dialogIntent); } catch (ActivityNotFoundException e) { - Log.e(TAG, "unable to start UsbAccessoryUriActivity"); + Slog.e(TAG, "unable to start UsbAccessoryUriActivity"); } } } @@ -652,7 +653,7 @@ class UsbSettingsManager { defaultRI.activityInfo.name)); mContext.startActivity(intent); } catch (ActivityNotFoundException e) { - Log.e(TAG, "startActivity failed", e); + Slog.e(TAG, "startActivity failed", e); } } else { Intent resolverIntent = new Intent(); @@ -679,7 +680,7 @@ class UsbSettingsManager { try { mContext.startActivity(resolverIntent); } catch (ActivityNotFoundException e) { - Log.e(TAG, "unable to start activity " + resolverIntent); + Slog.e(TAG, "unable to start activity " + resolverIntent); } } } @@ -733,7 +734,7 @@ class UsbSettingsManager { XmlUtils.nextElement(parser); } } catch (Exception e) { - Log.w(TAG, "Unable to load component info " + aInfo.toString(), e); + Slog.w(TAG, "Unable to load component info " + aInfo.toString(), e); } finally { if (parser != null) parser.close(); } @@ -751,7 +752,7 @@ class UsbSettingsManager { info = mPackageManager.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA); } catch (NameNotFoundException e) { - Log.e(TAG, "handlePackageUpdate could not find package " + packageName, e); + Slog.e(TAG, "handlePackageUpdate could not find package " + packageName, e); return; } @@ -831,7 +832,7 @@ class UsbSettingsManager { try { mContext.startActivity(intent); } catch (ActivityNotFoundException e) { - Log.e(TAG, "unable to start UsbPermissionActivity"); + Slog.e(TAG, "unable to start UsbPermissionActivity"); } finally { Binder.restoreCallingIdentity(identity); } @@ -847,7 +848,7 @@ class UsbSettingsManager { try { pi.send(mContext, 0, intent); } catch (PendingIntent.CanceledException e) { - Log.w(TAG, "requestPermission PendingIntent was cancelled"); + if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled"); } return; } @@ -867,7 +868,7 @@ class UsbSettingsManager { try { pi.send(mContext, 0, intent); } catch (PendingIntent.CanceledException e) { - Log.w(TAG, "requestPermission PendingIntent was cancelled"); + if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled"); } return; } diff --git a/services/jni/com_android_server_connectivity_Vpn.cpp b/services/jni/com_android_server_connectivity_Vpn.cpp index a0ea92b91a03..62d7636321c0 100644 --- a/services/jni/com_android_server_connectivity_Vpn.cpp +++ b/services/jni/com_android_server_connectivity_Vpn.cpp @@ -244,7 +244,7 @@ static int set_routes(const char *name, const char *routes) break; } - in_addr_t mask = prefix ? (~0 << (32 - prefix)) : 1; + in_addr_t mask = prefix ? (~0 << (32 - prefix)) : 0x80000000; *as_in_addr(&rt4.rt_genmask) = htonl(mask); if (ioctl(inet4, SIOCADDRT, &rt4) && errno != EEXIST) { count = (errno == EINVAL) ? BAD_ARGUMENT : SYSTEM_ERROR; @@ -394,7 +394,7 @@ static jint setRoutes(JNIEnv *env, jobject thiz, jstring jName, } count = set_routes(name, routes); if (count < 0) { - throwException(env, count, "Cannot set address"); + throwException(env, count, "Cannot set route"); count = -1; } diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java index 903f2b0b6f66..f2c28bb538c8 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java @@ -610,9 +610,6 @@ public class NetworkStatsServiceTest extends AndroidTestCase { mAlarmManager.setInexactRepeating( eq(AlarmManager.ELAPSED_REALTIME), anyLong(), anyLong(), isA(PendingIntent.class)); expectLastCall().atLeastOnce(); - - mNetManager.setBandwidthControlEnabled(true); - expectLastCall().atLeastOnce(); } private void expectNetworkState(NetworkState... state) throws Exception { @@ -633,7 +630,6 @@ public class NetworkStatsServiceTest extends AndroidTestCase { private void expectSettings(long persistThreshold, long bucketDuration, long maxHistory) throws Exception { - expect(mSettings.getEnabled()).andReturn(true).anyTimes(); expect(mSettings.getPollInterval()).andReturn(HOUR_IN_MILLIS).anyTimes(); expect(mSettings.getPersistThreshold()).andReturn(persistThreshold).anyTimes(); expect(mSettings.getNetworkBucketDuration()).andReturn(bucketDuration).anyTimes(); diff --git a/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java b/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java index dea67f35bfde..ffabb7b6e72e 100644 --- a/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java +++ b/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java @@ -94,9 +94,6 @@ public class PhoneNumberFormattingTextWatcher implements TextWatcher { */ public PhoneNumberFormattingTextWatcher(String countryCode) { if (countryCode == null) throw new IllegalArgumentException(); - // TODO: remove this once CountryDetector.detectCountry().getCountryIso() is fixed to always - // return uppercase. Tracked at b/4941319. - countryCode = countryCode.toUpperCase(Locale.ENGLISH); mFormatter = PhoneNumberUtil.getInstance().getAsYouTypeFormatter(countryCode); } diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index fce7cdc82440..2f010e57b161 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -473,8 +473,8 @@ public class ServiceState implements Parcelable { + " EmergOnly=" + mIsEmergencyOnly); } - public void setStateOutOfService() { - mState = STATE_OUT_OF_SERVICE; + private void setNullState(int state) { + mState = state; mRoaming = false; mOperatorAlphaLong = null; mOperatorAlphaShort = null; @@ -491,23 +491,12 @@ public class ServiceState implements Parcelable { mIsEmergencyOnly = false; } - // TODO - can't this be combined with the above method? + public void setStateOutOfService() { + setNullState(STATE_OUT_OF_SERVICE); + } + public void setStateOff() { - mState = STATE_POWER_OFF; - mRoaming = false; - mOperatorAlphaLong = null; - mOperatorAlphaShort = null; - mOperatorNumeric = null; - mIsManualNetworkSelection = false; - mRadioTechnology = 0; - mCssIndicator = false; - mNetworkId = -1; - mSystemId = -1; - mCdmaRoamingIndicator = -1; - mCdmaDefaultRoamingIndicator = -1; - mCdmaEriIconIndex = -1; - mCdmaEriIconMode = -1; - mIsEmergencyOnly = false; + setNullState(STATE_POWER_OFF); } public void setState(int state) { diff --git a/telephony/java/com/android/internal/telephony/CallerInfo.java b/telephony/java/com/android/internal/telephony/CallerInfo.java index ab93e2a90ea9..68e0045eda45 100644 --- a/telephony/java/com/android/internal/telephony/CallerInfo.java +++ b/telephony/java/com/android/internal/telephony/CallerInfo.java @@ -526,16 +526,6 @@ public class CallerInfo { + countryIso); } - // Temp workaround: The current libphonenumber library requires - // the countryIso to be uppercase (e.g. "US") but the - // detector.detectCountry().getCountryIso() call currently returns - // "us". Passing "us" to util.parse() will just result in a - // NumberParseException. - // So force the countryIso to uppercase for now. - // TODO: remove this once getCountryIso() is fixed to always - // return uppercase. - countryIso = countryIso.toUpperCase(); - PhoneNumber pn = null; try { if (VDBG) Log.v(TAG, "parsing '" + number diff --git a/telephony/java/com/android/internal/telephony/DefaultPhoneNotifier.java b/telephony/java/com/android/internal/telephony/DefaultPhoneNotifier.java index 910905aa4543..aa7568b108f8 100644 --- a/telephony/java/com/android/internal/telephony/DefaultPhoneNotifier.java +++ b/telephony/java/com/android/internal/telephony/DefaultPhoneNotifier.java @@ -21,6 +21,7 @@ import android.net.LinkProperties; import android.os.Bundle; import android.os.RemoteException; import android.os.ServiceManager; +import android.telephony.ServiceState; import android.telephony.TelephonyManager; import android.util.Log; @@ -55,8 +56,13 @@ public class DefaultPhoneNotifier implements PhoneNotifier { } public void notifyServiceState(Phone sender) { + ServiceState ss = sender.getServiceState(); + if (ss == null) { + ss = new ServiceState(); + ss.setStateOutOfService(); + } try { - mRegistry.notifyServiceState(sender.getServiceState()); + mRegistry.notifyServiceState(ss); } catch (RemoteException ex) { // system process is dead } diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java index 67f515888f22..a6b131a91106 100644 --- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java +++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java @@ -788,13 +788,13 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { Message msg = obtainMessage(EVENT_DISCONNECT_DONE, apnContext); apnContext.getDataConnection().tearDown(apnContext.getReason(), msg); apnContext.setState(State.DISCONNECTING); - } else { - // apn is connected but no reference to dcac. - // Should not be happen, but reset the state in case. - apnContext.setState(State.IDLE); - mPhone.notifyDataConnection(apnContext.getReason(), - apnContext.getApnType()); } + } else { + // apn is connected but no reference to dcac. + // Should not be happen, but reset the state in case. + apnContext.setState(State.IDLE); + mPhone.notifyDataConnection(apnContext.getReason(), + apnContext.getApnType()); } } } else { diff --git a/tests/GridLayoutTest/res/layout/grid3.xml b/tests/GridLayoutTest/res/layout/grid3.xml index ba911c269e4e..536be7eefa03 100644 --- a/tests/GridLayoutTest/res/layout/grid3.xml +++ b/tests/GridLayoutTest/res/layout/grid3.xml @@ -66,8 +66,8 @@ <Space android:layout_row="4" android:layout_column="2" - android:layout_rowWeight="1" - android:layout_columnWeight="1" + android:layout_heightSpec="canStretch" + android:layout_widthSpec="canStretch" /> <Button diff --git a/tests/GridLayoutTest/src/com/android/test/layout/Activity2.java b/tests/GridLayoutTest/src/com/android/test/layout/Activity2.java index e010a007529f..cba98c2b7538 100644 --- a/tests/GridLayoutTest/src/com/android/test/layout/Activity2.java +++ b/tests/GridLayoutTest/src/com/android/test/layout/Activity2.java @@ -97,8 +97,8 @@ public class Activity2 extends Activity { Space v = new Space(context); { LayoutParams lp = new LayoutParams(row5, col3); - lp.rowWeight = 1; - lp.columnWeight = 1; + lp.widthSpec = CAN_STRETCH; + lp.heightSpec = CAN_STRETCH; vg.addView(v, lp); } } diff --git a/tests/RenderScriptTests/PerfTest/AndroidManifest.xml b/tests/RenderScriptTests/PerfTest/AndroidManifest.xml index aafd176838b9..cc6039685736 100644 --- a/tests/RenderScriptTests/PerfTest/AndroidManifest.xml +++ b/tests/RenderScriptTests/PerfTest/AndroidManifest.xml @@ -9,8 +9,7 @@ android:icon="@drawable/test_pattern"> <uses-library android:name="android.test.runner" /> <activity android:name="RsBench" - android:label="RsBenchmark" - android:theme="@android:style/Theme.Black.NoTitleBar"> + android:label="RsBenchmark"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> diff --git a/tests/RenderScriptTests/PerfTest/res/menu/loader_menu.xml b/tests/RenderScriptTests/PerfTest/res/menu/loader_menu.xml new file mode 100644 index 000000000000..823467758827 --- /dev/null +++ b/tests/RenderScriptTests/PerfTest/res/menu/loader_menu.xml @@ -0,0 +1,25 @@ +<?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. +*/ +--> + +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:id="@+id/benchmark_mode" + android:title="@string/benchmark_mode" /> + <item android:id="@+id/debug_mode" + android:title="@string/debug_mode" /> +</menu> diff --git a/tests/RenderScriptTests/PerfTest/res/values/strings.xml b/tests/RenderScriptTests/PerfTest/res/values/strings.xml new file mode 100644 index 000000000000..627ac212db8d --- /dev/null +++ b/tests/RenderScriptTests/PerfTest/res/values/strings.xml @@ -0,0 +1,24 @@ +<?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. +*/ +--> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <skip /> + <string name="benchmark_mode">Benchmark Mode</string> + <string name="debug_mode">Debug Mode</string> +</resources> diff --git a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBench.java b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBench.java index d7393f8065cd..b336a4d03550 100644 --- a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBench.java +++ b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBench.java @@ -31,10 +31,14 @@ import android.provider.Settings.System; import android.util.Log; import android.view.Menu; import android.view.MenuItem; +import android.view.MenuInflater; import android.view.View; import android.view.Window; import android.widget.Button; import android.widget.ListView; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.widget.Toast; import java.lang.Runtime; @@ -77,4 +81,37 @@ public class RsBench extends Activity { super.onPause(); mView.pause(); } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.loader_menu, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle item selection + switch (item.getItemId()) { + case R.id.benchmark_mode: + mView.setBenchmarkMode(); + return true; + case R.id.debug_mode: + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("Pick a Test"); + builder.setItems(mView.getTestNames(), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + Toast.makeText(getApplicationContext(), + "Switching to: " + mView.getTestNames()[item], + Toast.LENGTH_SHORT).show(); + mView.setDebugMode(item); + } + }); + builder.show(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } } diff --git a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBenchRS.java b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBenchRS.java index c706286df685..c375be5e58bc 100644 --- a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBenchRS.java +++ b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBenchRS.java @@ -84,12 +84,6 @@ public class RsBenchRS { private Resources mRes; private RenderScriptGL mRS; - private Sampler mLinearClamp; - private Sampler mLinearWrap; - private Sampler mMipLinearWrap; - private Sampler mNearestClamp; - private Sampler mNearesWrap; - private ProgramStore mProgStoreBlendNoneDepth; private ProgramStore mProgStoreBlendNone; private ProgramStore mProgStoreBlendAlpha; @@ -115,10 +109,6 @@ public class RsBenchRS { private ScriptField_FragentShaderConstants3_s mFSConstPixel; - private ProgramRaster mCullBack; - private ProgramRaster mCullFront; - private ProgramRaster mCullNone; - private Allocation mTexTorus; private Allocation mTexOpaque; private Allocation mTexTransparent; @@ -143,6 +133,8 @@ public class RsBenchRS { private ScriptC_rsbench mScript; + private ScriptC_text_test mTextScript; + private ScriptC_torus_test mTorusScript; private final BitmapFactory.Options mOptionsARGB = new BitmapFactory.Options(); @@ -310,6 +302,7 @@ public class RsBenchRS { mProgStoreBlendAdd = BLEND_ADD_DEPTH_NONE(mRS); mScript.set_gProgStoreBlendNoneDepth(mProgStoreBlendNoneDepth); + mScript.set_gProgStoreBlendNone(mProgStoreBlendNone); mScript.set_gProgStoreBlendAlpha(mProgStoreBlendAlpha); mScript.set_gProgStoreBlendAdd(mProgStoreBlendAdd); @@ -330,22 +323,24 @@ public class RsBenchRS { texBuilder.setTexture(ProgramFragmentFixedFunction.Builder.EnvMode.REPLACE, ProgramFragmentFixedFunction.Builder.Format.RGBA, 0); mProgFragmentTexture = texBuilder.create(); - mProgFragmentTexture.bindSampler(mLinearClamp, 0); + mProgFragmentTexture.bindSampler(Sampler.CLAMP_LINEAR(mRS), 0); ProgramFragmentFixedFunction.Builder colBuilder = new ProgramFragmentFixedFunction.Builder(mRS); colBuilder.setVaryingColor(false); mProgFragmentColor = colBuilder.create(); mScript.set_gProgFragmentColor(mProgFragmentColor); + mScript.set_gProgFragmentTexture(mProgFragmentTexture); + // For Galaxy live wallpaper drawing ProgramFragmentFixedFunction.Builder builder = new ProgramFragmentFixedFunction.Builder(mRS); builder.setTexture(ProgramFragmentFixedFunction.Builder.EnvMode.REPLACE, ProgramFragmentFixedFunction.Builder.Format.RGB, 0); ProgramFragment pfb = builder.create(); - pfb.bindSampler(mNearesWrap, 0); + pfb.bindSampler(Sampler.WRAP_NEAREST(mRS), 0); mScript.set_gPFBackground(pfb); builder = new ProgramFragmentFixedFunction.Builder(mRS); @@ -354,7 +349,7 @@ public class RsBenchRS { ProgramFragmentFixedFunction.Builder.Format.RGBA, 0); builder.setVaryingColor(true); ProgramFragment pfs = builder.create(); - pfs.bindSampler(mMipLinearWrap, 0); + pfs.bindSampler(Sampler.WRAP_LINEAR_MIP_LINEAR(mRS), 0); mScript.set_gPFStars(pfs); } @@ -404,6 +399,7 @@ public class RsBenchRS { mScript.set_gProgVertex(mProgVertex); + // For galaxy live wallpaper mPvStarAlloc = new ScriptField_VpConsts(mRS, 1); mScript.bind_vpConstants(mPvStarAlloc); @@ -447,13 +443,11 @@ public class RsBenchRS { private void initCustomShaders() { mVSConst = new ScriptField_VertexShaderConstants_s(mRS, 1); mFSConst = new ScriptField_FragentShaderConstants_s(mRS, 1); - mScript.bind_gVSConstants(mVSConst); - mScript.bind_gFSConstants(mFSConst); + mVSConstPixel = new ScriptField_VertexShaderConstants3_s(mRS, 1); mFSConstPixel = new ScriptField_FragentShaderConstants3_s(mRS, 1); - mScript.bind_gVSConstPixel(mVSConstPixel); - mScript.bind_gFSConstPixel(mFSConstPixel); + // Initialize the shader builder ProgramVertex.Builder pvbCustom = new ProgramVertex.Builder(mRS); @@ -506,11 +500,7 @@ public class RsBenchRS { } mProgFragmentMultitex = pfbCustom.create(); - mScript.set_gProgVertexCustom(mProgVertexCustom); - mScript.set_gProgFragmentCustom(mProgFragmentCustom); - mScript.set_gProgVertexPixelLight(mProgVertexPixelLight); - mScript.set_gProgVertexPixelLightMove(mProgVertexPixelLightMove); - mScript.set_gProgFragmentPixelLight(mProgFragmentPixelLight); + mScript.set_gProgFragmentMultitex(mProgFragmentMultitex); } @@ -587,39 +577,22 @@ public class RsBenchRS { Log.e("rs", "could not load model"); } else { mTorus = (Mesh)entry.getObject(); - mScript.set_gTorusMesh(mTorus); } createParticlesMesh(); } private void initSamplers() { - Sampler.Builder bs = new Sampler.Builder(mRS); - bs.setMinification(Sampler.Value.LINEAR); - bs.setMagnification(Sampler.Value.LINEAR); - bs.setWrapS(Sampler.Value.WRAP); - bs.setWrapT(Sampler.Value.WRAP); - mLinearWrap = bs.create(); - - mLinearClamp = Sampler.CLAMP_LINEAR(mRS); - mNearestClamp = Sampler.CLAMP_NEAREST(mRS); - mMipLinearWrap = Sampler.WRAP_LINEAR_MIP_LINEAR(mRS); - mNearesWrap = Sampler.WRAP_NEAREST(mRS); - - mScript.set_gLinearClamp(mLinearClamp); - mScript.set_gLinearWrap(mLinearWrap); - mScript.set_gMipLinearWrap(mMipLinearWrap); - mScript.set_gNearestClamp(mNearestClamp); + mScript.set_gLinearClamp(Sampler.CLAMP_LINEAR(mRS)); + mScript.set_gLinearWrap(Sampler.WRAP_LINEAR(mRS)); + mScript.set_gMipLinearWrap(Sampler.WRAP_LINEAR_MIP_LINEAR(mRS)); + mScript.set_gNearestClamp(Sampler.CLAMP_NEAREST(mRS)); } private void initProgramRaster() { - mCullBack = ProgramRaster.CULL_BACK(mRS); - mCullFront = ProgramRaster.CULL_FRONT(mRS); - mCullNone = ProgramRaster.CULL_NONE(mRS); - - mScript.set_gCullBack(mCullBack); - mScript.set_gCullFront(mCullFront); - mScript.set_gCullNone(mCullNone); + mScript.set_gCullBack(ProgramRaster.CULL_BACK(mRS)); + mScript.set_gCullFront(ProgramRaster.CULL_FRONT(mRS)); + mScript.set_gCullNone(ProgramRaster.CULL_NONE(mRS)); } private int strlen(byte[] array) { @@ -645,9 +618,47 @@ public class RsBenchRS { } } + public void setDebugMode(int num) { + mScript.invoke_setDebugMode(num); + } + + public void setBenchmarkMode() { + mScript.invoke_setBenchmarkMode(); + } + + void initTextScript() { + mTextScript = new ScriptC_text_test(mRS, mRes, R.raw.text_test); + mTextScript.set_gFontSans(mFontSans); + mTextScript.set_gFontSerif(mFontSerif); + } + + void initTorusScript() { + mTorusScript = new ScriptC_torus_test(mRS, mRes, R.raw.torus_test); + mTorusScript.set_gCullFront(ProgramRaster.CULL_FRONT(mRS)); + mTorusScript.set_gCullBack(ProgramRaster.CULL_BACK(mRS)); + mTorusScript.set_gLinearClamp(Sampler.CLAMP_LINEAR(mRS)); + mTorusScript.set_gTorusMesh(mTorus); + mTorusScript.set_gTexTorus(mTexTorus); + mTorusScript.set_gProgVertexCustom(mProgVertexCustom); + mTorusScript.set_gProgFragmentCustom(mProgFragmentCustom); + mTorusScript.set_gProgVertexPixelLight(mProgVertexPixelLight); + mTorusScript.set_gProgVertexPixelLightMove(mProgVertexPixelLightMove); + mTorusScript.set_gProgFragmentPixelLight(mProgFragmentPixelLight); + mTorusScript.bind_gVSConstPixel(mVSConstPixel); + mTorusScript.bind_gFSConstPixel(mFSConstPixel); + mTorusScript.bind_gVSConstants(mVSConst); + mTorusScript.bind_gFSConstants(mFSConst); + mTorusScript.set_gProgVertex(mProgVertex); + mTorusScript.set_gProgFragmentTexture(mProgFragmentTexture); + mTorusScript.set_gProgFragmentColor(mProgFragmentColor); + mTorusScript.set_gProgStoreBlendNoneDepth(mProgStoreBlendNoneDepth); + } + private void initRS() { mScript = new ScriptC_rsbench(mRS, mRes, R.raw.rsbench); + + mRS.setMessageHandler(mRsMessage); mMaxModes = mScript.get_gMaxModes(); @@ -709,6 +720,13 @@ public class RsBenchRS { mSampleListViewAllocs.copyAll(); mScript.bind_gListViewText(mSampleListViewAllocs); + initTextScript(); + initTorusScript(); + + mScript.set_gFontScript(mTextScript); + mScript.set_gTorusScript(mTorusScript); + mScript.set_gDummyAlloc(Allocation.createSized(mRS, Element.I32(mRS), 1)); + mRS.bindRootScript(mScript); } } diff --git a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBenchView.java b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBenchView.java index 2882b931c589..61aa3e129395 100644 --- a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBenchView.java +++ b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBenchView.java @@ -105,4 +105,16 @@ public class RsBenchView extends RSSurfaceView { public boolean testIsFinished() { return mRender.testIsFinished(); } + + void setBenchmarkMode() { + mRender.setBenchmarkMode(); + } + + void setDebugMode(int num) { + mRender.setDebugMode(num); + } + + String[] getTestNames() { + return mRender.mTestNames; + } } diff --git a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/rsbench.rs b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/rsbench.rs index bb81862e4e30..eaafe1db3d40 100644 --- a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/rsbench.rs +++ b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/rsbench.rs @@ -18,6 +18,7 @@ #include "rs_graphics.rsh" #include "shader_def.rsh" +#include "subtest_def.rsh" /* Message sent from script to renderscript */ const int RS_MSG_TEST_DONE = 100; @@ -98,7 +99,6 @@ ListAllocs *gListViewText; rs_mesh g10by10Mesh; rs_mesh g100by100Mesh; rs_mesh gWbyHMesh; -rs_mesh gTorusMesh; rs_mesh gSingleMesh; rs_font gFontSans; @@ -115,25 +115,15 @@ rs_program_raster gCullBack; rs_program_raster gCullFront; rs_program_raster gCullNone; -// Custom vertex shader compunents -VertexShaderConstants *gVSConstants; -FragentShaderConstants *gFSConstants; -VertexShaderConstants3 *gVSConstPixel; -FragentShaderConstants3 *gFSConstPixel; // Export these out to easily set the inputs to shader VertexShaderInputs *gVSInputs; -// Custom shaders we use for lighting -rs_program_vertex gProgVertexCustom; -rs_program_fragment gProgFragmentCustom; -rs_program_vertex gProgVertexPixelLight; -rs_program_vertex gProgVertexPixelLightMove; -rs_program_fragment gProgFragmentPixelLight; + rs_program_fragment gProgFragmentMultitex; rs_allocation gRenderBufferColor; rs_allocation gRenderBufferDepth; -float gDt = 0; +static float gDt = 0; void init() { } @@ -141,16 +131,6 @@ void init() { static int gRenderSurfaceW; static int gRenderSurfaceH; -static const char *sampleText = "This is a sample of small text for performace"; -// Offsets for multiple layer of text -static int textOffsets[] = { 0, 0, -5, -5, 5, 5, -8, -8, 8, 8}; -static float textColors[] = {1.0f, 1.0f, 1.0f, 1.0f, - 0.5f, 0.7f, 0.5f, 1.0f, - 0.7f, 0.5f, 0.5f, 1.0f, - 0.5f, 0.5f, 0.7f, 1.0f, - 0.5f, 0.6f, 0.7f, 1.0f, -}; - /** * Methods to draw the galaxy live wall paper */ @@ -291,40 +271,16 @@ static void setupOffscreenTarget() { rsgBindDepthTarget(gRenderBufferDepth); } -static void displayFontSamples(int fillNum) { +rs_script gFontScript; +rs_script gTorusScript; +rs_allocation gDummyAlloc; - rs_font fonts[5]; - fonts[0] = gFontSans; - fonts[1] = gFontSerif; - fonts[2] = gFontSans; - fonts[3] = gFontSerif; - fonts[4] = gFontSans; - - uint width = gRenderSurfaceW; - uint height = gRenderSurfaceH; - int left = 0, right = 0, top = 0, bottom = 0; - rsgMeasureText(sampleText, &left, &right, &top, &bottom); - - int textHeight = top - bottom; - int textWidth = right - left; - int numVerticalLines = height / textHeight; - int yPos = top; - - int xOffset = 0, yOffset = 0; - for(int fillI = 0; fillI < fillNum; fillI ++) { - rsgBindFont(fonts[fillI]); - xOffset = textOffsets[fillI * 2]; - yOffset = textOffsets[fillI * 2 + 1]; - float *colPtr = textColors + fillI * 4; - rsgFontColor(colPtr[0], colPtr[1], colPtr[2], colPtr[3]); - for (int h = 0; h < 4; h ++) { - yPos = top + yOffset; - for (int v = 0; v < numVerticalLines; v ++) { - rsgDrawText(sampleText, xOffset + textWidth * h, yPos); - yPos += textHeight; - } - } - } +static void displayFontSamples(int fillNum) { + TestData testData; + testData.renderSurfaceW = gRenderSurfaceW; + testData.renderSurfaceH = gRenderSurfaceH; + testData.user = fillNum; + rsForEach(gFontScript, gDummyAlloc, gDummyAlloc, &testData); } static void bindProgramVertexOrtho() { @@ -555,212 +511,37 @@ static void displayLiveWallPaper(int wResolution, int hResolution) { drawMeshInPage(2.0f*gRenderSurfaceW, 0, wResolution, hResolution); } -static float gTorusRotation = 0; -static void updateModelMatrix(rs_matrix4x4 *matrix, void *buffer) { - if (buffer == 0) { - rsgProgramVertexLoadModelMatrix(matrix); - } else { - rsgAllocationSyncAll(rsGetAllocation(buffer)); - } -} - -static void drawToruses(int numMeshes, rs_matrix4x4 *matrix, void *buffer) { - - if (numMeshes == 1) { - rsMatrixLoadTranslate(matrix, 0.0f, 0.0f, -7.5f); - rsMatrixRotate(matrix, gTorusRotation, 1.0f, 0.0f, 0.0f); - updateModelMatrix(matrix, buffer); - rsgDrawMesh(gTorusMesh); - return; - } - - if (numMeshes == 2) { - rsMatrixLoadTranslate(matrix, -1.6f, 0.0f, -7.5f); - rsMatrixRotate(matrix, gTorusRotation, 1.0f, 0.0f, 0.0f); - updateModelMatrix(matrix, buffer); - rsgDrawMesh(gTorusMesh); - - rsMatrixLoadTranslate(matrix, 1.6f, 0.0f, -7.5f); - rsMatrixRotate(matrix, gTorusRotation, 1.0f, 0.0f, 0.0f); - updateModelMatrix(matrix, buffer); - rsgDrawMesh(gTorusMesh); - return; - } - - float startX = -5.0f; - float startY = -1.5f; - float startZ = -15.0f; - float dist = 3.2f; - - for (int h = 0; h < 4; h ++) { - for (int v = 0; v < 2; v ++) { - // Position our model on the screen - rsMatrixLoadTranslate(matrix, startX + dist * h, startY + dist * v, startZ); - rsMatrixRotate(matrix, gTorusRotation, 1.0f, 0.0f, 0.0f); - updateModelMatrix(matrix, buffer); - rsgDrawMesh(gTorusMesh); - } - } -} - // Quick hack to get some geometry numbers static void displaySimpleGeoSamples(bool useTexture, int numMeshes) { - rsgBindProgramVertex(gProgVertex); - rsgBindProgramRaster(gCullBack); - // Setup the projection matrix with 30 degree field of view - rs_matrix4x4 proj; - float aspect = (float)gRenderSurfaceW / (float)gRenderSurfaceH; - rsMatrixLoadPerspective(&proj, 30.0f, aspect, 0.1f, 100.0f); - rsgProgramVertexLoadProjectionMatrix(&proj); - - // Fragment shader with texture - rsgBindProgramStore(gProgStoreBlendNoneDepth); - if (useTexture) { - rsgBindProgramFragment(gProgFragmentTexture); - } else { - rsgBindProgramFragment(gProgFragmentColor); - rsgProgramFragmentConstantColor(gProgFragmentColor, 0.1, 0.7, 0.1, 1); - } - rsgBindSampler(gProgFragmentTexture, 0, gLinearClamp); - rsgBindTexture(gProgFragmentTexture, 0, gTexTorus); - - // Apply a rotation to our mesh - gTorusRotation += 50.0f * gDt; - if (gTorusRotation > 360.0f) { - gTorusRotation -= 360.0f; - } - - rs_matrix4x4 matrix; - drawToruses(numMeshes, &matrix, 0); -} - -float gLight0Rotation = 0; -float gLight1Rotation = 0; - -static void setupCustomShaderLights() { - float4 light0Pos = {-5.0f, 5.0f, -10.0f, 1.0f}; - float4 light1Pos = {2.0f, 5.0f, 15.0f, 1.0f}; - float4 light0DiffCol = {0.9f, 0.7f, 0.7f, 1.0f}; - float4 light0SpecCol = {0.9f, 0.6f, 0.6f, 1.0f}; - float4 light1DiffCol = {0.5f, 0.5f, 0.9f, 1.0f}; - float4 light1SpecCol = {0.5f, 0.5f, 0.9f, 1.0f}; - - gLight0Rotation += 50.0f * gDt; - if (gLight0Rotation > 360.0f) { - gLight0Rotation -= 360.0f; - } - gLight1Rotation -= 50.0f * gDt; - if (gLight1Rotation > 360.0f) { - gLight1Rotation -= 360.0f; - } - - rs_matrix4x4 l0Mat; - rsMatrixLoadRotate(&l0Mat, gLight0Rotation, 1.0f, 0.0f, 0.0f); - light0Pos = rsMatrixMultiply(&l0Mat, light0Pos); - rs_matrix4x4 l1Mat; - rsMatrixLoadRotate(&l1Mat, gLight1Rotation, 0.0f, 0.0f, 1.0f); - light1Pos = rsMatrixMultiply(&l1Mat, light1Pos); - - // Set light 0 properties - gVSConstants->light0_Posision = light0Pos; - gVSConstants->light0_Diffuse = 1.0f; - gVSConstants->light0_Specular = 0.5f; - gVSConstants->light0_CosinePower = 10.0f; - // Set light 1 properties - gVSConstants->light1_Posision = light1Pos; - gVSConstants->light1_Diffuse = 1.0f; - gVSConstants->light1_Specular = 0.7f; - gVSConstants->light1_CosinePower = 25.0f; - rsgAllocationSyncAll(rsGetAllocation(gVSConstants)); - - // Update fragment shader constants - // Set light 0 colors - gFSConstants->light0_DiffuseColor = light0DiffCol; - gFSConstants->light0_SpecularColor = light0SpecCol; - // Set light 1 colors - gFSConstants->light1_DiffuseColor = light1DiffCol; - gFSConstants->light1_SpecularColor = light1SpecCol; - rsgAllocationSyncAll(rsGetAllocation(gFSConstants)); - - // Set light 0 properties for per pixel lighting - gFSConstPixel->light0_Posision = light0Pos; - gFSConstPixel->light0_Diffuse = 1.0f; - gFSConstPixel->light0_Specular = 0.5f; - gFSConstPixel->light0_CosinePower = 10.0f; - gFSConstPixel->light0_DiffuseColor = light0DiffCol; - gFSConstPixel->light0_SpecularColor = light0SpecCol; - // Set light 1 properties - gFSConstPixel->light1_Posision = light1Pos; - gFSConstPixel->light1_Diffuse = 1.0f; - gFSConstPixel->light1_Specular = 0.7f; - gFSConstPixel->light1_CosinePower = 25.0f; - gFSConstPixel->light1_DiffuseColor = light1DiffCol; - gFSConstPixel->light1_SpecularColor = light1SpecCol; - rsgAllocationSyncAll(rsGetAllocation(gFSConstPixel)); + TestData testData; + testData.renderSurfaceW = gRenderSurfaceW; + testData.renderSurfaceH = gRenderSurfaceH; + testData.dt = gDt; + testData.user = 0; + testData.user1 = useTexture ? 1 : 0; + testData.user2 = numMeshes; + rsForEach(gTorusScript, gDummyAlloc, gDummyAlloc, &testData); } static void displayCustomShaderSamples(int numMeshes) { - - // Update vertex shader constants - // Load model matrix - // Apply a rotation to our mesh - gTorusRotation += 50.0f * gDt; - if (gTorusRotation > 360.0f) { - gTorusRotation -= 360.0f; - } - - // Setup the projection matrix - float aspect = (float)gRenderSurfaceW / (float)gRenderSurfaceH; - rsMatrixLoadPerspective(&gVSConstants->proj, 30.0f, aspect, 0.1f, 100.0f); - setupCustomShaderLights(); - - rsgBindProgramVertex(gProgVertexCustom); - - // Fragment shader with texture - rsgBindProgramStore(gProgStoreBlendNoneDepth); - rsgBindProgramFragment(gProgFragmentCustom); - rsgBindSampler(gProgFragmentCustom, 0, gLinearClamp); - rsgBindTexture(gProgFragmentCustom, 0, gTexTorus); - - // Use back face culling - rsgBindProgramRaster(gCullBack); - - drawToruses(numMeshes, &gVSConstants->model, gVSConstants); + TestData testData; + testData.renderSurfaceW = gRenderSurfaceW; + testData.renderSurfaceH = gRenderSurfaceH; + testData.dt = gDt; + testData.user = 1; + testData.user1 = numMeshes; + rsForEach(gTorusScript, gDummyAlloc, gDummyAlloc, &testData); } static void displayPixelLightSamples(int numMeshes, bool heavyVertex) { - - // Update vertex shader constants - // Load model matrix - // Apply a rotation to our mesh - gTorusRotation += 30.0f * gDt; - if (gTorusRotation > 360.0f) { - gTorusRotation -= 360.0f; - } - - gVSConstPixel->time = rsUptimeMillis()*0.005; - - // Setup the projection matrix - float aspect = (float)gRenderSurfaceW / (float)gRenderSurfaceH; - rsMatrixLoadPerspective(&gVSConstPixel->proj, 30.0f, aspect, 0.1f, 100.0f); - setupCustomShaderLights(); - - if (heavyVertex) { - rsgBindProgramVertex(gProgVertexPixelLightMove); - } else { - rsgBindProgramVertex(gProgVertexPixelLight); - } - - // Fragment shader with texture - rsgBindProgramStore(gProgStoreBlendNoneDepth); - rsgBindProgramFragment(gProgFragmentPixelLight); - rsgBindSampler(gProgFragmentPixelLight, 0, gLinearClamp); - rsgBindTexture(gProgFragmentPixelLight, 0, gTexTorus); - - // Use back face culling - rsgBindProgramRaster(gCullBack); - - drawToruses(numMeshes, &gVSConstPixel->model, gVSConstPixel); + TestData testData; + testData.renderSurfaceW = gRenderSurfaceW; + testData.renderSurfaceH = gRenderSurfaceH; + testData.dt = gDt; + testData.user = 2; + testData.user1 = numMeshes; + testData.user2 = heavyVertex ? 1 : 0; + rsForEach(gTorusScript, gDummyAlloc, gDummyAlloc, &testData); } static void displayMultitextureSample(bool blend, int quadCount) { @@ -862,6 +643,20 @@ static const char *testNames[] = { "UI test with live wallpaper", }; +static bool gIsDebugMode = false; +void setDebugMode(int testNumber) { + gIsDebugMode = true; + benchMode = testNumber; + rsgClearAllRenderTargets(); +} + +void setBenchmarkMode() { + gIsDebugMode = false; + benchMode = 0; + runningLoops = 0; +} + + void getTestName(int testIndex) { int bufferLen = rsAllocationGetDimX(rsGetAllocation(gStringBuffer)); if (testIndex >= gMaxModes) { @@ -932,7 +727,7 @@ static void runTest(int index) { displaySingletexFill(true, 10); break; case 18: - displayMultitextureSample(true, 8); + displayMultitextureSample(true, 10); break; case 19: displayPixelLightSamples(1, false); @@ -992,14 +787,7 @@ static void drawOffscreenResult(int posX, int posY, int width, int height) { startX + width, startY, 0, 1, 1); } -int root(void) { - gRenderSurfaceW = rsgGetWidth(); - gRenderSurfaceH = rsgGetHeight(); - rsgClearColor(0.2f, 0.2f, 0.2f, 1.0f); - rsgClearDepth(1.0f); - if(!checkInit()) { - return 1; - } +static void benchmark() { gDt = 1.0f / 60.0f; @@ -1045,8 +833,6 @@ int root(void) { benchMode ++; - gTorusRotation = 0; - if (benchMode == gMaxModes) { rsSendToClientBlocking(RS_MSG_RESULTS_READY, gResultBuffer, gMaxModes*sizeof(float)); benchMode = 0; @@ -1058,5 +844,30 @@ int root(void) { sendMsgFlag = true; } } + +} + +static void debug() { + gDt = rsGetDt(); + + rsgFinish(); + runTest(benchMode); +} + +int root(void) { + gRenderSurfaceW = rsgGetWidth(); + gRenderSurfaceH = rsgGetHeight(); + rsgClearColor(0.2f, 0.2f, 0.2f, 1.0f); + rsgClearDepth(1.0f); + if(!checkInit()) { + return 1; + } + + if (gIsDebugMode) { + debug(); + } else { + benchmark(); + } + return 1; } diff --git a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/subtest_def.rsh b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/subtest_def.rsh new file mode 100644 index 000000000000..b635373b2afa --- /dev/null +++ b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/subtest_def.rsh @@ -0,0 +1,28 @@ +// 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. + +#pragma version(1) + +#pragma rs java_package_name(com.android.perftest) + +typedef struct TestData_s { + int renderSurfaceW; + int renderSurfaceH; + float dt; + int user; + int user1; + int user2; + int user3; +} TestData; + diff --git a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/text_test.rs b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/text_test.rs new file mode 100644 index 000000000000..0df6b3590bd5 --- /dev/null +++ b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/text_test.rs @@ -0,0 +1,82 @@ +// 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. + +#pragma version(1) + +#pragma rs java_package_name(com.android.perftest) + +#include "rs_graphics.rsh" +#include "subtest_def.rsh" + +rs_font gFontSans; +rs_font gFontSerif; + +void init() { +} + +static int gRenderSurfaceW = 1280; +static int gRenderSurfaceH = 720; + +static const char *sampleText = "This is a sample of small text for performace"; +// Offsets for multiple layer of text +static int textOffsets[] = { 0, 0, -5, -5, 5, 5, -8, -8, 8, 8}; +static float textColors[] = {1.0f, 1.0f, 1.0f, 1.0f, + 0.5f, 0.7f, 0.5f, 1.0f, + 0.7f, 0.5f, 0.5f, 1.0f, + 0.5f, 0.5f, 0.7f, 1.0f, + 0.5f, 0.6f, 0.7f, 1.0f, +}; + +static void displayFontSamples(int fillNum) { + + rs_font fonts[5]; + fonts[0] = gFontSans; + fonts[1] = gFontSerif; + fonts[2] = gFontSans; + fonts[3] = gFontSerif; + fonts[4] = gFontSans; + + uint width = gRenderSurfaceW; + uint height = gRenderSurfaceH; + int left = 0, right = 0, top = 0, bottom = 0; + rsgMeasureText(sampleText, &left, &right, &top, &bottom); + + int textHeight = top - bottom; + int textWidth = right - left; + int numVerticalLines = height / textHeight; + int yPos = top; + + int xOffset = 0, yOffset = 0; + for(int fillI = 0; fillI < fillNum; fillI ++) { + rsgBindFont(fonts[fillI]); + xOffset = textOffsets[fillI * 2]; + yOffset = textOffsets[fillI * 2 + 1]; + float *colPtr = textColors + fillI * 4; + rsgFontColor(colPtr[0], colPtr[1], colPtr[2], colPtr[3]); + for (int h = 0; h < 4; h ++) { + yPos = top + yOffset; + for (int v = 0; v < numVerticalLines; v ++) { + rsgDrawText(sampleText, xOffset + textWidth * h, yPos); + yPos += textHeight; + } + } + } +} + +void root(const void *v_in, void *v_out, const void *usrData, uint32_t x, uint32_t y) { + TestData *testData = (TestData*)usrData; + gRenderSurfaceW = testData->renderSurfaceW; + gRenderSurfaceH = testData->renderSurfaceH; + displayFontSamples(testData->user); +} diff --git a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/torus_test.rs b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/torus_test.rs new file mode 100644 index 000000000000..26d56807f6f2 --- /dev/null +++ b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/torus_test.rs @@ -0,0 +1,283 @@ +// 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. + +#pragma version(1) + +#pragma rs java_package_name(com.android.perftest) + +#include "rs_graphics.rsh" +#include "subtest_def.rsh" +#include "shader_def.rsh" + +rs_program_vertex gProgVertex; +rs_program_fragment gProgFragmentColor; +rs_program_fragment gProgFragmentTexture; + +rs_program_store gProgStoreBlendNoneDepth; +rs_mesh gTorusMesh; + +rs_program_raster gCullBack; +rs_program_raster gCullFront; + +// Custom vertex shader compunents +VertexShaderConstants *gVSConstants; +FragentShaderConstants *gFSConstants; +VertexShaderConstants3 *gVSConstPixel; +FragentShaderConstants3 *gFSConstPixel; + +// Custom shaders we use for lighting +rs_program_vertex gProgVertexCustom; +rs_program_fragment gProgFragmentCustom; + +rs_sampler gLinearClamp; +rs_allocation gTexTorus; + +rs_program_vertex gProgVertexPixelLight; +rs_program_vertex gProgVertexPixelLightMove; +rs_program_fragment gProgFragmentPixelLight; + +static float gDt = 0.0f; + +static int gRenderSurfaceW; +static int gRenderSurfaceH; + + +static float gTorusRotation = 0; +static void updateModelMatrix(rs_matrix4x4 *matrix, void *buffer) { + if (buffer == 0) { + rsgProgramVertexLoadModelMatrix(matrix); + } else { + rsgAllocationSyncAll(rsGetAllocation(buffer)); + } +} + +static void drawToruses(int numMeshes, rs_matrix4x4 *matrix, void *buffer) { + + if (numMeshes == 1) { + rsMatrixLoadTranslate(matrix, 0.0f, 0.0f, -7.5f); + rsMatrixRotate(matrix, gTorusRotation, 1.0f, 0.0f, 0.0f); + updateModelMatrix(matrix, buffer); + rsgDrawMesh(gTorusMesh); + return; + } + + if (numMeshes == 2) { + rsMatrixLoadTranslate(matrix, -1.6f, 0.0f, -7.5f); + rsMatrixRotate(matrix, gTorusRotation, 1.0f, 0.0f, 0.0f); + updateModelMatrix(matrix, buffer); + rsgDrawMesh(gTorusMesh); + + rsMatrixLoadTranslate(matrix, 1.6f, 0.0f, -7.5f); + rsMatrixRotate(matrix, gTorusRotation, 1.0f, 0.0f, 0.0f); + updateModelMatrix(matrix, buffer); + rsgDrawMesh(gTorusMesh); + return; + } + + float startX = -5.0f; + float startY = -1.5f; + float startZ = -15.0f; + float dist = 3.2f; + + for (int h = 0; h < 4; h ++) { + for (int v = 0; v < 2; v ++) { + // Position our model on the screen + rsMatrixLoadTranslate(matrix, startX + dist * h, startY + dist * v, startZ); + rsMatrixRotate(matrix, gTorusRotation, 1.0f, 0.0f, 0.0f); + updateModelMatrix(matrix, buffer); + rsgDrawMesh(gTorusMesh); + } + } +} + + +// Quick hack to get some geometry numbers +static void displaySimpleGeoSamples(bool useTexture, int numMeshes) { + rsgBindProgramVertex(gProgVertex); + rsgBindProgramRaster(gCullBack); + // Setup the projection matrix with 30 degree field of view + rs_matrix4x4 proj; + float aspect = (float)gRenderSurfaceW / (float)gRenderSurfaceH; + rsMatrixLoadPerspective(&proj, 30.0f, aspect, 0.1f, 100.0f); + rsgProgramVertexLoadProjectionMatrix(&proj); + + // Fragment shader with texture + rsgBindProgramStore(gProgStoreBlendNoneDepth); + if (useTexture) { + rsgBindProgramFragment(gProgFragmentTexture); + } else { + rsgBindProgramFragment(gProgFragmentColor); + rsgProgramFragmentConstantColor(gProgFragmentColor, 0.1, 0.7, 0.1, 1); + } + rsgBindSampler(gProgFragmentTexture, 0, gLinearClamp); + rsgBindTexture(gProgFragmentTexture, 0, gTexTorus); + + // Apply a rotation to our mesh + gTorusRotation += 50.0f * gDt; + if (gTorusRotation > 360.0f) { + gTorusRotation -= 360.0f; + } + + rs_matrix4x4 matrix; + drawToruses(numMeshes, &matrix, 0); +} + +float gLight0Rotation = 0; +float gLight1Rotation = 0; + +static void setupCustomShaderLights() { + float4 light0Pos = {-5.0f, 5.0f, -10.0f, 1.0f}; + float4 light1Pos = {2.0f, 5.0f, 15.0f, 1.0f}; + float4 light0DiffCol = {0.9f, 0.7f, 0.7f, 1.0f}; + float4 light0SpecCol = {0.9f, 0.6f, 0.6f, 1.0f}; + float4 light1DiffCol = {0.5f, 0.5f, 0.9f, 1.0f}; + float4 light1SpecCol = {0.5f, 0.5f, 0.9f, 1.0f}; + + gLight0Rotation += 50.0f * gDt; + if (gLight0Rotation > 360.0f) { + gLight0Rotation -= 360.0f; + } + gLight1Rotation -= 50.0f * gDt; + if (gLight1Rotation > 360.0f) { + gLight1Rotation -= 360.0f; + } + + rs_matrix4x4 l0Mat; + rsMatrixLoadRotate(&l0Mat, gLight0Rotation, 1.0f, 0.0f, 0.0f); + light0Pos = rsMatrixMultiply(&l0Mat, light0Pos); + rs_matrix4x4 l1Mat; + rsMatrixLoadRotate(&l1Mat, gLight1Rotation, 0.0f, 0.0f, 1.0f); + light1Pos = rsMatrixMultiply(&l1Mat, light1Pos); + + // Set light 0 properties + gVSConstants->light0_Posision = light0Pos; + gVSConstants->light0_Diffuse = 1.0f; + gVSConstants->light0_Specular = 0.5f; + gVSConstants->light0_CosinePower = 10.0f; + // Set light 1 properties + gVSConstants->light1_Posision = light1Pos; + gVSConstants->light1_Diffuse = 1.0f; + gVSConstants->light1_Specular = 0.7f; + gVSConstants->light1_CosinePower = 25.0f; + rsgAllocationSyncAll(rsGetAllocation(gVSConstants)); + + // Update fragment shader constants + // Set light 0 colors + gFSConstants->light0_DiffuseColor = light0DiffCol; + gFSConstants->light0_SpecularColor = light0SpecCol; + // Set light 1 colors + gFSConstants->light1_DiffuseColor = light1DiffCol; + gFSConstants->light1_SpecularColor = light1SpecCol; + rsgAllocationSyncAll(rsGetAllocation(gFSConstants)); + + // Set light 0 properties for per pixel lighting + gFSConstPixel->light0_Posision = light0Pos; + gFSConstPixel->light0_Diffuse = 1.0f; + gFSConstPixel->light0_Specular = 0.5f; + gFSConstPixel->light0_CosinePower = 10.0f; + gFSConstPixel->light0_DiffuseColor = light0DiffCol; + gFSConstPixel->light0_SpecularColor = light0SpecCol; + // Set light 1 properties + gFSConstPixel->light1_Posision = light1Pos; + gFSConstPixel->light1_Diffuse = 1.0f; + gFSConstPixel->light1_Specular = 0.7f; + gFSConstPixel->light1_CosinePower = 25.0f; + gFSConstPixel->light1_DiffuseColor = light1DiffCol; + gFSConstPixel->light1_SpecularColor = light1SpecCol; + rsgAllocationSyncAll(rsGetAllocation(gFSConstPixel)); +} + +static void displayCustomShaderSamples(int numMeshes) { + + // Update vertex shader constants + // Load model matrix + // Apply a rotation to our mesh + gTorusRotation += 50.0f * gDt; + if (gTorusRotation > 360.0f) { + gTorusRotation -= 360.0f; + } + + // Setup the projection matrix + float aspect = (float)gRenderSurfaceW / (float)gRenderSurfaceH; + rsMatrixLoadPerspective(&gVSConstants->proj, 30.0f, aspect, 0.1f, 100.0f); + setupCustomShaderLights(); + + rsgBindProgramVertex(gProgVertexCustom); + + // Fragment shader with texture + rsgBindProgramStore(gProgStoreBlendNoneDepth); + rsgBindProgramFragment(gProgFragmentCustom); + rsgBindSampler(gProgFragmentCustom, 0, gLinearClamp); + rsgBindTexture(gProgFragmentCustom, 0, gTexTorus); + + // Use back face culling + rsgBindProgramRaster(gCullBack); + + drawToruses(numMeshes, &gVSConstants->model, gVSConstants); +} + +static void displayPixelLightSamples(int numMeshes, bool heavyVertex) { + + // Update vertex shader constants + // Load model matrix + // Apply a rotation to our mesh + gTorusRotation += 30.0f * gDt; + if (gTorusRotation > 360.0f) { + gTorusRotation -= 360.0f; + } + + gVSConstPixel->time = rsUptimeMillis()*0.005; + + // Setup the projection matrix + float aspect = (float)gRenderSurfaceW / (float)gRenderSurfaceH; + rsMatrixLoadPerspective(&gVSConstPixel->proj, 30.0f, aspect, 0.1f, 100.0f); + setupCustomShaderLights(); + + if (heavyVertex) { + rsgBindProgramVertex(gProgVertexPixelLightMove); + } else { + rsgBindProgramVertex(gProgVertexPixelLight); + } + + // Fragment shader with texture + rsgBindProgramStore(gProgStoreBlendNoneDepth); + rsgBindProgramFragment(gProgFragmentPixelLight); + rsgBindSampler(gProgFragmentPixelLight, 0, gLinearClamp); + rsgBindTexture(gProgFragmentPixelLight, 0, gTexTorus); + + // Use back face culling + rsgBindProgramRaster(gCullBack); + + drawToruses(numMeshes, &gVSConstPixel->model, gVSConstPixel); +} + + +void root(const void *v_in, void *v_out, const void *usrData, uint32_t x, uint32_t y) { + TestData *testData = (TestData*)usrData; + gRenderSurfaceW = testData->renderSurfaceW; + gRenderSurfaceH = testData->renderSurfaceH; + gDt = testData->dt; + + switch(testData->user) { + case 0: + displaySimpleGeoSamples(testData->user1 == 1 ? true : false, testData->user2); + break; + case 1: + displayCustomShaderSamples(testData->user1); + break; + case 2: + displayPixelLightSamples(testData->user1, testData->user2 == 1 ? true : false); + break; + } +} diff --git a/voip/java/com/android/server/sip/SipWakeupTimer.java b/voip/java/com/android/server/sip/SipWakeupTimer.java index 76780c02b3df..00d47ac63ce6 100644 --- a/voip/java/com/android/server/sip/SipWakeupTimer.java +++ b/voip/java/com/android/server/sip/SipWakeupTimer.java @@ -83,7 +83,7 @@ class SipWakeupTimer extends BroadcastReceiver { mEventQueue = null; } - private synchronized boolean stopped() { + private boolean stopped() { if (mEventQueue == null) { Log.w(TAG, "Timer stopped"); return true; @@ -233,7 +233,7 @@ class SipWakeupTimer extends BroadcastReceiver { } @Override - public void onReceive(Context context, Intent intent) { + public synchronized void onReceive(Context context, Intent intent) { // This callback is already protected by AlarmManager's wake lock. String action = intent.getAction(); if (getAction().equals(action) @@ -261,7 +261,7 @@ class SipWakeupTimer extends BroadcastReceiver { } } - private synchronized void execute(long triggerTime) { + private void execute(long triggerTime) { if (DEBUG_TIMER) Log.d(TAG, "time's up, triggerTime = " + showTime(triggerTime) + ": " + mEventQueue.size()); if (stopped() || mEventQueue.isEmpty()) return; |