diff options
167 files changed, 4775 insertions, 2026 deletions
diff --git a/api/13.xml b/api/13.xml index 9af0640c728a..2bfa04ed8bc1 100644 --- a/api/13.xml +++ b/api/13.xml @@ -25290,7 +25290,7 @@ abstract="false" static="false" final="false" - deprecated="not deprecated" + deprecated="deprecated" visibility="public" > <constructor name="ActivityGroup" @@ -30614,6 +30614,17 @@ visibility="public" > </method> +<method name="isDetached" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="isHidden" return="boolean" abstract="false" @@ -33935,7 +33946,7 @@ abstract="false" static="false" final="false" - deprecated="not deprecated" + deprecated="deprecated" visibility="public" > <constructor name="LocalActivityManager" @@ -37009,7 +37020,7 @@ abstract="false" static="false" final="false" - deprecated="not deprecated" + deprecated="deprecated" visibility="public" > <constructor name="TabActivity" @@ -61810,6 +61821,28 @@ visibility="public" > </field> +<field name="FEATURE_SCREEN_LANDSCAPE" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.hardware.screen.landscape"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="FEATURE_SCREEN_PORTRAIT" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.hardware.screen.portrait"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="FEATURE_SENSOR_ACCELEROMETER" type="java.lang.String" transient="false" @@ -148973,6 +149006,17 @@ visibility="public" > </field> +<field name="TWEET_TRANSACTION" + type="int" + transient="false" + volatile="false" + value="1599362900" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> </interface> <interface name="IBinder.DeathRecipient" abstract="true" diff --git a/api/current.txt b/api/current.txt index 2ad044e33099..e2f7f8b618c4 100644 --- a/api/current.txt +++ b/api/current.txt @@ -601,8 +601,10 @@ package android { field public static final int layout_height = 16842997; // 0x10100f5 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 field public static final int layout_marginLeft = 16842999; // 0x10100f7 field public static final int layout_marginRight = 16843001; // 0x10100f9 + field public static final int layout_marginStart = 16843674; // 0x101039a 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 @@ -7417,6 +7419,7 @@ package android.graphics { method public static final android.graphics.Bitmap.CompressFormat[] values(); enum_constant public static final android.graphics.Bitmap.CompressFormat JPEG; enum_constant public static final android.graphics.Bitmap.CompressFormat PNG; + enum_constant public static final android.graphics.Bitmap.CompressFormat WEBP; } public static final class Bitmap.Config extends java.lang.Enum { @@ -15277,11 +15280,11 @@ package android.provider { public final class ContactsContract { ctor public ContactsContract(); + field public static final java.lang.String ALLOW_PROFILE = "allow_profile"; field public static final java.lang.String AUTHORITY = "com.android.contacts"; field public static final android.net.Uri AUTHORITY_URI; field public static final java.lang.String CALLER_IS_SYNCADAPTER = "caller_is_syncadapter"; field public static final java.lang.String DIRECTORY_PARAM_KEY = "directory"; - field public static final java.lang.String INCLUDE_PROFILE = "include_profile"; field public static final java.lang.String LIMIT_PARAM_KEY = "limit"; field public static final java.lang.String PRIMARY_ACCOUNT_NAME = "name_for_primary_account"; field public static final java.lang.String PRIMARY_ACCOUNT_TYPE = "type_for_primary_account"; @@ -17492,7 +17495,7 @@ package android.security { public final class KeyChain { ctor public KeyChain(); - method public static void choosePrivateKeyAlias(android.app.Activity, android.security.KeyChainAliasCallback, java.lang.String[], java.security.Principal[], java.lang.String, int); + method public static void choosePrivateKeyAlias(android.app.Activity, android.security.KeyChainAliasCallback, java.lang.String[], java.security.Principal[], java.lang.String, int, java.lang.String); method public static java.security.cert.X509Certificate[] getCertificateChain(android.content.Context, java.lang.String) throws java.lang.InterruptedException, android.security.KeyChainException; method public static java.security.PrivateKey getPrivateKey(android.content.Context, java.lang.String) throws java.lang.InterruptedException, android.security.KeyChainException; } @@ -22845,8 +22848,10 @@ package android.view.accessibility { field public static final int TYPE_VIEW_HOVER_ENTER = 128; // 0x80 field public static final int TYPE_VIEW_HOVER_EXIT = 256; // 0x100 field public static final int TYPE_VIEW_LONG_CLICKED = 2; // 0x2 + field public static final int TYPE_VIEW_SCROLLED = 4096; // 0x1000 field public static final int TYPE_VIEW_SELECTED = 4; // 0x4 field public static final int TYPE_VIEW_TEXT_CHANGED = 16; // 0x10 + field public static final int TYPE_VIEW_TEXT_SELECTION_CHANGED = 8192; // 0x2000 field public static final int TYPE_WINDOW_CONTENT_CHANGED = 2048; // 0x800 field public static final int TYPE_WINDOW_STATE_CHANGED = 32; // 0x20 } @@ -22895,6 +22900,7 @@ package android.view.accessibility { method public boolean isFocused(); method public boolean isLongClickable(); method public boolean isPassword(); + method public boolean isScrollable(); method public boolean isSelected(); method public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View); method public static android.view.accessibility.AccessibilityNodeInfo obtain(); @@ -22914,6 +22920,7 @@ package android.view.accessibility { method public void setPackageName(java.lang.CharSequence); method public void setParent(android.view.View); method public void setPassword(boolean); + method public void setScrollable(boolean); method public void setSelected(boolean); method public void setSource(android.view.View); method public void setText(java.lang.CharSequence); @@ -22935,13 +22942,17 @@ package android.view.accessibility { method public int getItemCount(); method public android.os.Parcelable getParcelableData(); method public int getRemovedCount(); + method public int getScrollX(); + method public int getScrollY(); method public android.view.accessibility.AccessibilityNodeInfo getSource(); method public java.util.List<java.lang.CharSequence> getText(); + method public int getToIndex(); method public int getWindowId(); method public boolean isChecked(); method public boolean isEnabled(); method public boolean isFullScreen(); method public boolean isPassword(); + method public boolean isScrollable(); method public static android.view.accessibility.AccessibilityRecord obtain(android.view.accessibility.AccessibilityRecord); method public static android.view.accessibility.AccessibilityRecord obtain(); method public void recycle(); @@ -22958,7 +22969,11 @@ package android.view.accessibility { method public void setParcelableData(android.os.Parcelable); method public void setPassword(boolean); method public void setRemovedCount(int); + method public void setScrollX(int); + method public void setScrollY(int); + method public void setScrollable(boolean); method public void setSource(android.view.View); + method public void setToIndex(int); } } diff --git a/cmds/keystore/keystore.cpp b/cmds/keystore/keystore.cpp index bbd1a1bfd60c..1c1f37a34c7b 100644 --- a/cmds/keystore/keystore.cpp +++ b/cmds/keystore/keystore.cpp @@ -708,7 +708,7 @@ static struct user { uid_t euid; uint32_t perms; } users[] = { - {AID_SYSTEM, ~0, ~GET}, + {AID_SYSTEM, ~0, ~0}, {AID_VPN, AID_SYSTEM, GET}, {AID_WIFI, AID_SYSTEM, GET}, {AID_ROOT, AID_SYSTEM, GET}, diff --git a/cmds/keystore/test-keystore b/cmds/keystore/test-keystore index 82b276fe7666..3be51b3e7bfb 100755 --- a/cmds/keystore/test-keystore +++ b/cmds/keystore/test-keystore @@ -116,11 +116,12 @@ function test_basic() { expect "1 No error" expect "baz" - log "system does not have access to read any keys" + log "get baz" keystore system g baz - expect "6 Permission denied" - - log "however, root can read system user keys (as can wifi or vpn users)" + expect "1 No error" + expect "quux" + + log "root can read system user keys (as can wifi or vpn users)" keystore root g baz expect "1 No error" expect "quux" diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index e927f6cd47fb..208869b6c2fd 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -1573,7 +1573,7 @@ public class PackageParser { boolean hardwareAccelerated = sa.getBoolean( com.android.internal.R.styleable.AndroidManifestApplication_hardwareAccelerated, - false); + owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH); if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestApplication_hasCode, diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java index 0be17764b56b..8a4269313213 100644 --- a/core/java/android/hardware/Camera.java +++ b/core/java/android/hardware/Camera.java @@ -288,7 +288,8 @@ public class Camera { * you can call {@link #reconnect()} to reclaim the camera. * * <p>This must be done before calling - * {@link android.media.MediaRecorder#setCamera(Camera)}. + * {@link android.media.MediaRecorder#setCamera(Camera)}. This cannot be + * called after recording starts. * * <p>If you are not recording video, you probably do not need this method. * @@ -301,6 +302,11 @@ public class Camera { * Camera objects are locked by default unless {@link #unlock()} is * called. Normally {@link #reconnect()} is used instead. * + * <p>Since API level 13, camera is automatically locked for applications in + * {@link android.media.MediaRecorder#start()}. Applications can use the + * camera (ex: zoom) after recording starts. There is no need to call this + * after recording starts or stops. + * * <p>If you are not recording video, you probably do not need this method. * * @throws RuntimeException if the camera cannot be re-locked (for @@ -315,9 +321,10 @@ public class Camera { * which will re-acquire the lock and allow you to continue using the * camera. * - * <p>This must be done after {@link android.media.MediaRecorder} is - * done recording if {@link android.media.MediaRecorder#setCamera(Camera)} - * was used. + * <p>Since API level 13, camera is automatically locked for applications in + * {@link android.media.MediaRecorder#start()}. Applications can use the + * camera (ex: zoom) after recording starts. There is no need to call this + * after recording starts or stops. * * <p>If you are not recording video, you probably do not need this method. * @@ -827,7 +834,9 @@ public class Camera { * <p>This method is only valid when preview is active (after * {@link #startPreview()}). Preview will be stopped after the image is * taken; callers must call {@link #startPreview()} again if they want to - * re-start preview or take more pictures. + * re-start preview or take more pictures. This should not be called between + * {@link android.media.MediaRecorder#start()} and + * {@link android.media.MediaRecorder#stop()}. * * <p>After calling this method, you must not call {@link #startPreview()} * or take another picture until the JPEG callback has returned. diff --git a/core/java/android/net/EthernetDataTracker.java b/core/java/android/net/EthernetDataTracker.java index 6f922476d06c..a866436ed77d 100644 --- a/core/java/android/net/EthernetDataTracker.java +++ b/core/java/android/net/EthernetDataTracker.java @@ -53,6 +53,7 @@ public class EthernetDataTracker implements NetworkStateTracker { private AtomicInteger mDefaultGatewayAddr = new AtomicInteger(0); private AtomicBoolean mDefaultRouteSet = new AtomicBoolean(false); + private static boolean mLinkUp; private LinkProperties mLinkProperties; private LinkCapabilities mLinkCapabilities; private NetworkInfo mNetworkInfo; @@ -74,8 +75,25 @@ public class EthernetDataTracker implements NetworkStateTracker { mTracker = tracker; } - public void interfaceLinkStatusChanged(String iface, boolean up) { - Log.d(TAG, "Interface " + iface + " link " + (up ? "up" : "down")); + public void interfaceStatusChanged(String iface, boolean up) { + Log.d(TAG, "Interface status changed: " + iface + (up ? "up" : "down")); + } + + public void interfaceLinkStateChanged(String iface, boolean up) { + if (mIface.equals(iface) && mLinkUp != up) { + Log.d(TAG, "Interface " + iface + " link " + (up ? "up" : "down")); + mLinkUp = up; + + // use DHCP + if (up) { + mTracker.reconnect(); + } else { + NetworkUtils.stopDhcp(mIface); + mTracker.mNetworkInfo.setIsAvailable(false); + mTracker.mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, + null, null); + } + } } public void interfaceAdded(String iface) { @@ -91,6 +109,7 @@ public class EthernetDataTracker implements NetworkStateTracker { mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_ETHERNET, 0, NETWORKTYPE, ""); mLinkProperties = new LinkProperties(); mLinkCapabilities = new LinkCapabilities(); + mLinkUp = false; mNetworkInfo.setIsAvailable(false); setTeardownRequested(false); @@ -182,14 +201,11 @@ public class EthernetDataTracker implements NetworkStateTracker { // register for notifications from NetworkManagement Service IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); + mInterfaceObserver = new InterfaceObserver(this); - try { - service.registerObserver(mInterfaceObserver); - } catch (RemoteException e) { - Log.e(TAG, "Could not register InterfaceObserver " + e); - } - // connect to an ethernet interface that already exists + // enable and try to connect to an ethernet interface that + // already exists sIfaceMatch = context.getResources().getString( com.android.internal.R.string.config_ethernet_iface_regex); try { @@ -197,6 +213,8 @@ public class EthernetDataTracker implements NetworkStateTracker { for (String iface : ifaces) { if (iface.matches(sIfaceMatch)) { mIface = iface; + InterfaceConfiguration config = service.getInterfaceConfig(iface); + mLinkUp = config.isActive(); reconnect(); break; } @@ -204,6 +222,12 @@ public class EthernetDataTracker implements NetworkStateTracker { } catch (RemoteException e) { Log.e(TAG, "Could not get list of interfaces " + e); } + + try { + service.registerObserver(mInterfaceObserver); + } catch (RemoteException e) { + Log.e(TAG, "Could not register InterfaceObserver " + e); + } } /** diff --git a/core/java/android/net/INetworkManagementEventObserver.aidl b/core/java/android/net/INetworkManagementEventObserver.aidl index d30b63dab806..4436e6e444ee 100644 --- a/core/java/android/net/INetworkManagementEventObserver.aidl +++ b/core/java/android/net/INetworkManagementEventObserver.aidl @@ -23,12 +23,21 @@ package android.net; */ interface INetworkManagementEventObserver { /** - * Interface link status has changed. + * Interface configuration status has changed. * * @param iface The interface. - * @param link True if link is up. + * @param up True if the interface has been enabled. */ - void interfaceLinkStatusChanged(String iface, boolean link); + void interfaceStatusChanged(String iface, boolean up); + + /** + * Interface physical-layer link state has changed. For Ethernet, + * this method is invoked when the cable is plugged in or unplugged. + * + * @param iface The interface. + * @param up True if the physical link-layer connection signal is valid. + */ + void interfaceLinkStateChanged(String iface, boolean up); /** * An interface has been added to the system diff --git a/core/java/android/net/http/SslCertificate.java b/core/java/android/net/http/SslCertificate.java index 5079c23ceb64..f35002ad244c 100644 --- a/core/java/android/net/http/SslCertificate.java +++ b/core/java/android/net/http/SslCertificate.java @@ -16,15 +16,19 @@ package android.net.http; +import android.content.Context; import android.os.Bundle; +import android.text.format.DateFormat; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextView; +import java.security.cert.X509Certificate; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Vector; -import java.security.cert.X509Certificate; - import com.android.org.bouncycastle.asn1.DERObjectIdentifier; import com.android.org.bouncycastle.asn1.x509.X509Name; @@ -58,7 +62,7 @@ public class SslCertificate { */ private Date mValidNotAfter; - /** + /** * Bundle key names */ private static final String ISSUED_TO = "issued-to"; @@ -108,8 +112,10 @@ public class SslCertificate { * Creates a new SSL certificate object * @param issuedTo The entity this certificate is issued to * @param issuedBy The entity that issued this certificate - * @param validNotBefore The not-before date from the certificate validity period in ISO 8601 format - * @param validNotAfter The not-after date from the certificate validity period in ISO 8601 format + * @param validNotBefore The not-before date from the certificate + * validity period in ISO 8601 format + * @param validNotAfter The not-after date from the certificate + * validity period in ISO 8601 format * @deprecated Use {@link #SslCertificate(X509Certificate)} */ @Deprecated @@ -202,9 +208,8 @@ public class SslCertificate { * @return A string representation of this certificate for debugging */ public String toString() { - return - "Issued to: " + mIssuedTo.getDName() + ";\n" + - "Issued by: " + mIssuedBy.getDName() + ";\n"; + return ("Issued to: " + mIssuedTo.getDName() + ";\n" + + "Issued by: " + mIssuedBy.getDName() + ";\n"); } /** @@ -328,4 +333,65 @@ public class SslCertificate { return mUName != null ? mUName : ""; } } + + /** + * Inflates the SSL certificate view (helper method). + * @return The resultant certificate view with issued-to, issued-by, + * issued-on, expires-on, and possibly other fields set. + * If the input certificate is null, returns null. + * + * @hide Used by Browser and Settings + */ + public View inflateCertificateView(Context context) { + LayoutInflater factory = LayoutInflater.from(context); + + View certificateView = factory.inflate( + com.android.internal.R.layout.ssl_certificate, null); + + // issued to: + SslCertificate.DName issuedTo = getIssuedTo(); + if (issuedTo != null) { + ((TextView) certificateView.findViewById(com.android.internal.R.id.to_common)) + .setText(issuedTo.getCName()); + ((TextView) certificateView.findViewById(com.android.internal.R.id.to_org)) + .setText(issuedTo.getOName()); + ((TextView) certificateView.findViewById(com.android.internal.R.id.to_org_unit)) + .setText(issuedTo.getUName()); + } + + // issued by: + SslCertificate.DName issuedBy = getIssuedBy(); + if (issuedBy != null) { + ((TextView) certificateView.findViewById(com.android.internal.R.id.by_common)) + .setText(issuedBy.getCName()); + ((TextView) certificateView.findViewById(com.android.internal.R.id.by_org)) + .setText(issuedBy.getOName()); + ((TextView) certificateView.findViewById(com.android.internal.R.id.by_org_unit)) + .setText(issuedBy.getUName()); + } + + // issued on: + String issuedOn = formatCertificateDate(context, getValidNotBeforeDate()); + ((TextView) certificateView.findViewById(com.android.internal.R.id.issued_on)) + .setText(issuedOn); + + // expires on: + String expiresOn = formatCertificateDate(context, getValidNotAfterDate()); + ((TextView) certificateView.findViewById(com.android.internal.R.id.expires_on)) + .setText(expiresOn); + + return certificateView; + } + + /** + * Formats the certificate date to a properly localized date string. + * @return Properly localized version of the certificate date string and + * the "" if it fails to localize. + */ + private String formatCertificateDate(Context context, Date certificateDate) { + if (certificateDate == null) { + return ""; + } + return DateFormat.getDateFormat(context).format(certificateDate); + } } diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 1b28aa2bcc6b..6c7c58dc85af 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -278,6 +278,16 @@ public class Build { /** * Current version under development. + * + * <p>Applications targeting this or a later release will get these + * new changes in behavior:</p> + * <ul> + * <li> 2d drawing hardware acceleration is now turned on by default. + * You can use + * {@link android.R.attr#hardwareAccelerated android:hardwareAccelerated} + * to turn it off if needed, although this is strongly discouraged since + * it will result in poor performance on larger screen devices. + * </ul> */ public static final int ICE_CREAM_SANDWICH = CUR_DEVELOPMENT; } diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index a17983a0fd37..21373ec89715 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -313,8 +313,8 @@ public class PowerManager */ public void release(int flags) { synchronized (mToken) { - mHandler.removeCallbacks(mReleaser); if (!mRefCounted || --mCount == 0) { + mHandler.removeCallbacks(mReleaser); try { mService.releaseWakeLock(mToken, flags); } catch (RemoteException e) { diff --git a/core/java/android/pim/EventRecurrence.java b/core/java/android/pim/EventRecurrence.java index cde7dac18891..830f63f3749a 100644 --- a/core/java/android/pim/EventRecurrence.java +++ b/core/java/android/pim/EventRecurrence.java @@ -456,6 +456,19 @@ public class EventRecurrence { return true; } + /** + * Determines whether this rule specifies a simple monthly rule by weekday, such as + * "FREQ=MONTHLY;BYDAY=3TU" (the 3rd Tuesday of every month). + * <p> + * Negative days, e.g. "FREQ=MONTHLY;BYDAY=-1TU" (the last Tuesday of every month), + * will cause "false" to be returned. + * <p> + * Rules that fire every week, such as "FREQ=MONTHLY;BYDAY=TU" (every Tuesday of every + * month) will cause "false" to be returned. (Note these are usually expressed as + * WEEKLY rules, and hence are uncommon.) + * + * @return true if this rule is of the appropriate form + */ public boolean repeatsMonthlyOnDayCount() { if (this.freq != MONTHLY) { return false; @@ -465,6 +478,10 @@ public class EventRecurrence { return false; } + if (bydayNum[0] <= 0) { + return false; + } + return true; } diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index b5a11ab60784..cb96bfd2c9d1 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -123,14 +123,16 @@ public final class ContactsContract { /** * An optional URI parameter for selection queries that instructs the - * provider to include the user's personal profile contact entry (if any) - * in the contact results. If present, the user's profile will always be - * the first entry returned. The default value is false. + * provider to allow the user's personal profile contact entry (if any) + * to appear in a list of contact results. It is only useful when issuing + * a query that may retrieve more than one contact. If present, the user's + * profile will always be the first entry returned. The default value is + * false. * * Specifying this parameter will result in a security error if the calling * application does not have android.permission.READ_PROFILE permission. */ - public static final String INCLUDE_PROFILE = "include_profile"; + public static final String ALLOW_PROFILE = "allow_profile"; /** * A query parameter key used to specify the package that is requesting a query. diff --git a/core/java/android/provider/SyncConstValue.java b/core/java/android/provider/SyncConstValue.java index 2fcf315c6c34..027aed12a360 100644 --- a/core/java/android/provider/SyncConstValue.java +++ b/core/java/android/provider/SyncConstValue.java @@ -18,7 +18,7 @@ package android.provider; /** * Columns for tables that are synced to a server. - * @deprecated + * @deprecated Do not use. * @hide */ public interface SyncConstValue diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java index 5dbda9030868..6c3d387a5fd7 100755 --- a/core/java/android/view/KeyEvent.java +++ b/core/java/android/view/KeyEvent.java @@ -2361,11 +2361,11 @@ public class KeyEvent extends InputEvent implements Parcelable { * Gets the {@link KeyCharacterMap} associated with the keyboard device. * * @return The associated key character map. - * @throws {@link UnavailableException} if the key character map + * @throws {@link KeyCharacterMap.UnavailableException} if the key character map * could not be loaded because it was malformed or the default key character map * is missing from the system. * - * @see {@link KeyCharacterMap#load} + * @see KeyCharacterMap#load */ public final KeyCharacterMap getKeyCharacterMap() { return KeyCharacterMap.load(mDeviceId); diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java index 0c0cd7629eea..d656f315a4db 100644 --- a/core/java/android/view/TextureView.java +++ b/core/java/android/view/TextureView.java @@ -185,7 +185,7 @@ public class TextureView extends View { protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - if (isHardwareAccelerated() && mLayer != null) { + if (mLayer != null) { if (mListener != null) { mListener.onSurfaceTextureDestroyed(mSurface); } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 0413b05fbc65..574313441d15 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -2326,6 +2326,7 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit private CheckForLongPress mPendingCheckForLongPress; private CheckForTap mPendingCheckForTap = null; private PerformClick mPerformClick; + private SendViewScrolledAccessibilityEvent mSendViewScrolledAccessibilityEvent; private UnsetPressedState mUnsetPressedState; @@ -3699,7 +3700,6 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit * @see #dispatchPopulateAccessibilityEvent(AccessibilityEvent) */ public void onPopulateAccessibilityEvent(AccessibilityEvent event) { - } /** @@ -3728,12 +3728,23 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit event.setEnabled(isEnabled()); event.setContentDescription(mContentDescription); - if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED && mAttachInfo != null) { - ArrayList<View> focusablesTempList = mAttachInfo.mFocusablesTempList; - getRootView().addFocusables(focusablesTempList, View.FOCUS_FORWARD, FOCUSABLES_ALL); - event.setItemCount(focusablesTempList.size()); - event.setCurrentItemIndex(focusablesTempList.indexOf(this)); - focusablesTempList.clear(); + final int eventType = event.getEventType(); + switch (eventType) { + case AccessibilityEvent.TYPE_VIEW_FOCUSED: { + if (mAttachInfo != null) { + ArrayList<View> focusablesTempList = mAttachInfo.mFocusablesTempList; + getRootView().addFocusables(focusablesTempList, View.FOCUS_FORWARD, + FOCUSABLES_ALL); + event.setItemCount(focusablesTempList.size()); + event.setCurrentItemIndex(focusablesTempList.indexOf(this)); + focusablesTempList.clear(); + } + } break; + case AccessibilityEvent.TYPE_VIEW_SCROLLED: { + event.setScrollX(mScrollX); + event.setScrollY(mScrollY); + event.setItemCount(getHeight()); + } break; } } @@ -6165,6 +6176,16 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit } /** + * Remove the pending callback for sending a + * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event. + */ + private void removeSendViewScrolledAccessibilityEventCallback() { + if (mSendViewScrolledAccessibilityEvent != null) { + removeCallbacks(mSendViewScrolledAccessibilityEvent); + } + } + + /** * Sets the TouchDelegate for this View. */ public void setTouchDelegate(TouchDelegate delegate) { @@ -6337,6 +6358,10 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit * @param oldt Previous vertical scroll origin. */ protected void onScrollChanged(int l, int t, int oldl, int oldt) { + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + postSendViewScrolledAccessibilityEventCallback(); + } + mBackgroundSizeChanged = true; final AttachInfo ai = mAttachInfo; @@ -8263,6 +8288,22 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit } /** + * Post a callback to send a {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event. + * This event is sent at most once every + * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}. + */ + private void postSendViewScrolledAccessibilityEventCallback() { + if (mSendViewScrolledAccessibilityEvent == null) { + mSendViewScrolledAccessibilityEvent = new SendViewScrolledAccessibilityEvent(); + } + if (!mSendViewScrolledAccessibilityEvent.mIsPending) { + mSendViewScrolledAccessibilityEvent.mIsPending = true; + postDelayed(mSendViewScrolledAccessibilityEvent, + ViewConfiguration.getSendRecurringAccessibilityEventsInterval()); + } + } + + /** * Called by a parent to request that a child update its values for mScrollX * and mScrollY if necessary. This will typically be done if the child is * animating a scroll using a {@link android.widget.Scroller Scroller} @@ -9019,6 +9060,7 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit removeUnsetPressCallback(); removeLongPressCallback(); removePerformClickCallback(); + removeSendViewScrolledAccessibilityEventCallback(); destroyDrawingCache(); @@ -11830,6 +11872,10 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit mPrivateFlags |= FORCE_LAYOUT; mPrivateFlags |= INVALIDATED; + if (mLayoutParams != null) { + mLayoutParams.resolveWithDirection(getResolvedLayoutDirection()); + } + if (mParent != null && !mParent.isLayoutRequested()) { mParent.requestLayout(); } @@ -13816,6 +13862,18 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit host.invalidate(true); } } + } + + /** + * Resuable callback for sending + * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event. + */ + private class SendViewScrolledAccessibilityEvent implements Runnable { + public volatile boolean mIsPending; + public void run() { + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED); + mIsPending = false; + } } } diff --git a/core/java/android/view/ViewAncestor.java b/core/java/android/view/ViewAncestor.java index e5d6d9f2cb61..2b692f3e93f1 100644 --- a/core/java/android/view/ViewAncestor.java +++ b/core/java/android/view/ViewAncestor.java @@ -136,13 +136,6 @@ public final class ViewAncestor extends Handler implements ViewParent, static final ArrayList<ComponentCallbacks> sConfigCallbacks = new ArrayList<ComponentCallbacks>(); - /** - * Delay before dispatching an accessibility event that the window - * content has changed. The window content is considered changed - * after a layout pass. - */ - private static final long SEND_WINDOW_CONTENT_CHANGED_DELAY_MILLIS = 500; - long mLastTrackballTime = 0; final TrackballAxis mTrackballAxisX = new TrackballAxis(); final TrackballAxis mTrackballAxisY = new TrackballAxis(); @@ -284,7 +277,7 @@ public final class ViewAncestor extends Handler implements ViewParent, AccessibilityInteractionConnectionManager mAccessibilityInteractionConnectionManager; - SendWindowContentChanged mSendWindowContentChanged; + SendWindowContentChangedAccessibilityEvent mSendWindowContentChangedAccessibilityEvent; private final int mDensity; @@ -3692,14 +3685,19 @@ public final class ViewAncestor extends Handler implements ViewParent, /** * Post a callback to send a * {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event. + * This event is send at most once every + * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}. */ private void postSendWindowContentChangedCallback() { - if (mSendWindowContentChanged == null) { - mSendWindowContentChanged = new SendWindowContentChanged(); - } else { - removeCallbacks(mSendWindowContentChanged); + if (mSendWindowContentChangedAccessibilityEvent == null) { + mSendWindowContentChangedAccessibilityEvent = + new SendWindowContentChangedAccessibilityEvent(); + } + if (!mSendWindowContentChangedAccessibilityEvent.mIsPending) { + mSendWindowContentChangedAccessibilityEvent.mIsPending = true; + postDelayed(mSendWindowContentChangedAccessibilityEvent, + ViewConfiguration.getSendRecurringAccessibilityEventsInterval()); } - postDelayed(mSendWindowContentChanged, SEND_WINDOW_CONTENT_CHANGED_DELAY_MILLIS); } /** @@ -3707,8 +3705,8 @@ public final class ViewAncestor extends Handler implements ViewParent, * {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event. */ private void removeSendWindowContentChangedCallback() { - if (mSendWindowContentChanged != null) { - removeCallbacks(mSendWindowContentChanged); + if (mSendWindowContentChangedAccessibilityEvent != null) { + removeCallbacks(mSendWindowContentChangedAccessibilityEvent); } } @@ -4634,10 +4632,19 @@ public final class ViewAncestor extends Handler implements ViewParent, } } - private class SendWindowContentChanged implements Runnable { + private class SendWindowContentChangedAccessibilityEvent implements Runnable { + public volatile boolean mIsPending; + public void run() { if (mView != null) { - mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); + // Send the event directly since we do not want to append the + // source text because this is the text for the entire window + // and we just want to notify that the content has changed. + AccessibilityEvent event = AccessibilityEvent.obtain( + AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); + mView.onInitializeAccessibilityEvent(event); + AccessibilityManager.getInstance(mView.mContext).sendAccessibilityEvent(event); + mIsPending = false; } } } diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java index 5919150dbfae..f3a5050001ea 100644 --- a/core/java/android/view/ViewConfiguration.java +++ b/core/java/android/view/ViewConfiguration.java @@ -176,6 +176,14 @@ public class ViewConfiguration { private static final int TOUCH_EXPLORATION_TAP_SLOP = 80; /** + * Delay before dispatching a recurring accessibility event in milliseconds. + * This delay guarantees that a recurring event will be send at most once + * during the {@link #SEND_RECURRING_ACCESSIBILITY_EVENTS_INTERVAL_MILLIS} time + * frame. + */ + private static final long SEND_RECURRING_ACCESSIBILITY_EVENTS_INTERVAL_MILLIS = 400; + + /** * The maximum size of View's drawing cache, expressed in bytes. This size * should be at least equal to the size of the screen in ARGB888 format. */ @@ -498,6 +506,19 @@ public class ViewConfiguration { } /** + * Interval for dispatching a recurring accessibility event in milliseconds. + * This interval guarantees that a recurring event will be send at most once + * during the {@link #getSendRecurringAccessibilityEventsInterval()} time frame. + * + * @return The delay in milliseconds. + * + * @hide + */ + public static long getSendRecurringAccessibilityEventsInterval() { + return SEND_RECURRING_ACCESSIBILITY_EVENTS_INTERVAL_MILLIS; + } + + /** * @return Distance a touch must be outside the bounds of a window for it * to be counted as outside the window for purposes of dismissing that * window. diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index a21f8bb2a4a9..640539893961 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -5175,6 +5175,21 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** + * Resolve layout parameters depending on the layout direction. Subclasses that care about + * layoutDirection changes should override this method. The default implementation does + * nothing. + * + * @param layoutDirection the direction of the layout + * + * {@link View#LAYOUT_DIRECTION_LTR} + * {@link View#LAYOUT_DIRECTION_RTL} + * + * @hide + */ + protected void resolveWithDirection(int layoutDirection) { + } + + /** * Returns a String representation of this set of layout parameters. * * @param output the String to prepend to the internal representation @@ -5215,30 +5230,56 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager */ public static class MarginLayoutParams extends ViewGroup.LayoutParams { /** - * The left margin in pixels of the child. + * The left margin in pixels of the child. Whenever this value is changed, a call to + * {@link android.view.View#requestLayout()} needs to be done. */ @ViewDebug.ExportedProperty(category = "layout") public int leftMargin; /** - * The top margin in pixels of the child. + * The top margin in pixels of the child. Whenever this value is changed, a call to + * {@link android.view.View#requestLayout()} needs to be done. */ @ViewDebug.ExportedProperty(category = "layout") public int topMargin; /** - * The right margin in pixels of the child. + * The right margin in pixels of the child. Whenever this value is changed, a call to + * {@link android.view.View#requestLayout()} needs to be done. */ @ViewDebug.ExportedProperty(category = "layout") public int rightMargin; /** - * The bottom margin in pixels of the child. + * The bottom margin in pixels of the child. Whenever this value is changed, a call to + * {@link android.view.View#requestLayout()} needs to be done. */ @ViewDebug.ExportedProperty(category = "layout") public int bottomMargin; /** + * The start margin in pixels of the child. + * + * @hide + * + */ + @ViewDebug.ExportedProperty(category = "layout") + protected int startMargin = DEFAULT_RELATIVE; + + /** + * The end margin in pixels of the child. + * + * @hide + */ + @ViewDebug.ExportedProperty(category = "layout") + protected int endMargin = DEFAULT_RELATIVE; + + /** + * The default start and end margin. + */ + static private final int DEFAULT_RELATIVE = Integer.MIN_VALUE; + + /** * Creates a new set of layout parameters. The values are extracted from * the supplied attributes set and context. * @@ -5270,6 +5311,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager R.styleable.ViewGroup_MarginLayout_layout_marginRight, 0); bottomMargin = a.getDimensionPixelSize( R.styleable.ViewGroup_MarginLayout_layout_marginBottom, 0); + startMargin = a.getDimensionPixelSize( + R.styleable.ViewGroup_MarginLayout_layout_marginStart, DEFAULT_RELATIVE); + endMargin = a.getDimensionPixelSize( + R.styleable.ViewGroup_MarginLayout_layout_marginEnd, DEFAULT_RELATIVE); } a.recycle(); @@ -5295,6 +5340,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager this.topMargin = source.topMargin; this.rightMargin = source.rightMargin; this.bottomMargin = source.bottomMargin; + this.startMargin = source.startMargin; + this.endMargin = source.endMargin; } /** @@ -5305,7 +5352,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** - * Sets the margins, in pixels. + * Sets the margins, in pixels. A call to {@link android.view.View#requestLayout()} needs + * to be done so that the new margins are taken into account. Left and right margins may be + * overriden by {@link android.view.View#requestLayout()} depending on layout direction. * * @param left the left margin size * @param top the top margin size @@ -5323,6 +5372,92 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager rightMargin = right; bottomMargin = bottom; } + + /** + * Sets the relative margins, in pixels. A call to {@link android.view.View#requestLayout()} + * needs to be done so that the new relative margins are taken into account. Left and right + * margins may be overriden by {@link android.view.View#requestLayout()} depending on layout + * direction. + * + * @param start the start margin size + * @param top the top margin size + * @param end the right margin size + * @param bottom the bottom margin size + * + * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginStart + * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginTop + * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginEnd + * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginBottom + * + * @hide + */ + public void setMarginsRelative(int start, int top, int end, int bottom) { + startMargin = start; + topMargin = top; + endMargin = end; + bottomMargin = bottom; + } + + /** + * Returns the start margin in pixels. + * + * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginStart + * + * @return the start margin in pixels. + * + * @hide + */ + public int getMarginStart() { + return startMargin; + } + + /** + * Returns the end margin in pixels. + * + * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginEnd + * + * @return the end margin in pixels. + * + * @hide + */ + public int getMarginEnd() { + return endMargin; + } + + /** + * Check if margins are relative. + * + * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginStart + * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginEnd + * + * @return true if either marginStart or marginEnd has been set + * + * @hide + */ + public boolean isMarginRelative() { + return (startMargin != DEFAULT_RELATIVE) || (endMargin != DEFAULT_RELATIVE); + } + + /** + * This will be called by {@link android.view.View#requestLayout()}. Left and Right margins + * maybe overriden depending on layout direction. + * + * @hide + */ + @Override + protected void resolveWithDirection(int layoutDirection) { + switch(layoutDirection) { + case View.LAYOUT_DIRECTION_RTL: + leftMargin = (endMargin > DEFAULT_RELATIVE) ? endMargin : leftMargin; + rightMargin = (startMargin > DEFAULT_RELATIVE) ? startMargin : rightMargin; + break; + case View.LAYOUT_DIRECTION_LTR: + default: + leftMargin = (startMargin > DEFAULT_RELATIVE) ? startMargin : leftMargin; + rightMargin = (endMargin > DEFAULT_RELATIVE) ? endMargin : rightMargin; + break; + } + } } /* Describes a touched view and the ids of the pointers that it has captured. diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java index 30d64893837f..25f01a7dc9fa 100644 --- a/core/java/android/view/accessibility/AccessibilityEvent.java +++ b/core/java/android/view/accessibility/AccessibilityEvent.java @@ -56,76 +56,132 @@ import java.util.List; * <b>View clicked</b> - represents the event of clicking on a {@link android.view.View} * like {@link android.widget.Button}, {@link android.widget.CompoundButton}, etc. <br> * Type:{@link #TYPE_VIEW_CLICKED} <br> - * Properties: - * {@link #getClassName()}, - * {@link #getPackageName()}, - * {@link #getEventTime()}, - * {@link #getText()}, - * {@link #isChecked()}, - * {@link #isEnabled()}, - * {@link #isPassword()}, - * {@link #getItemCount()}, - * {@link #getCurrentItemIndex()} + * Properties:</br> + * <ul> + * <li>{@link #getSource()} - The source info (for registered clients).</li> * + * <li>{@link #getClassName()} - The class name of the source.</li> + * <li>{@link #getPackageName()} - The package name of the source.</li> + * <li>{@link #getEventTime()} - The event time.</li> + * <li>{@link #getText()} - The text of the source.</li> + * <li>{@link #isEnabled()} - Whether the source is enabled.</li> + * <li>{@link #isPassword()} - Whether the source is password.</li> + * <li>{@link #isChecked()} - Whether the source is checked.</li> + * </ul> * <p> * <b>View long clicked</b> - represents the event of long clicking on a {@link android.view.View} * like {@link android.widget.Button}, {@link android.widget.CompoundButton}, etc. <br> * Type:{@link #TYPE_VIEW_LONG_CLICKED} <br> - * Properties: - * {@link #getClassName()}, - * {@link #getPackageName()}, - * {@link #getEventTime()}, - * {@link #getText()}, - * {@link #isChecked()}, - * {@link #isEnabled()}, - * {@link #isPassword()}, - * {@link #getItemCount()}, - * {@link #getCurrentItemIndex()} + * Properties:</br> + * <ul> + * <li>{@link #getSource()} - The source info (for registered clients).</li> * + * <li>{@link #getClassName()} - The class name of the source.</li> + * <li>{@link #getPackageName()} - The package name of the source.</li> + * <li>{@link #getEventTime()} - The event time.</li> + * <li>{@link #getText()} - The text of the source.</li> + * <li>{@link #isEnabled()} - Whether the source is enabled.</li> + * <li>{@link #isPassword()} - Whether the source is password.</li> + * <li>{@link #isChecked()} - Whether the source is checked.</li> + * </ul> * <p> * <b>View selected</b> - represents the event of selecting an item usually in * the context of an {@link android.widget.AdapterView}. <br> * Type: {@link #TYPE_VIEW_SELECTED} <br> - * Properties: - * {@link #getClassName()}, - * {@link #getPackageName()}, - * {@link #getEventTime()}, - * {@link #getText()}, - * {@link #isChecked()}, - * {@link #isEnabled()}, - * {@link #isPassword()}, - * {@link #getItemCount()}, - * {@link #getCurrentItemIndex()} + * Properties:</br> + * <ul> + * <li>{@link #getSource()} - The source info (for registered clients).</li> * + * <li>{@link #getClassName()} - The class name of the source.</li> + * <li>{@link #getPackageName()} - The package name of the source.</li> + * <li>{@link #getEventTime()} - The event time.</li> + * <li>{@link #getText()} - The text of the source.</li> + * <li>{@link #isEnabled()} - Whether the source is enabled.</li> + * <li>{@link #isPassword()} - Whether the source is password.</li> + * <li>{@link #isChecked()} - Whether the source is checked.</li> + * <li>{@link #getItemCount()} -The number of selectable items of the source.</li> + * <li>{@link #getCurrentItemIndex()} - The currently selected item index.</li> + * </ul> + * <p> * <p> * <b>View focused</b> - represents the event of focusing a * {@link android.view.View}. <br> * Type: {@link #TYPE_VIEW_FOCUSED} <br> - * Properties: - * {@link #getClassName()}, - * {@link #getPackageName()}, - * {@link #getEventTime()}, - * {@link #getText()}, - * {@link #isChecked()}, - * {@link #isEnabled()}, - * {@link #isPassword()}, - * {@link #getItemCount()}, - * {@link #getCurrentItemIndex()} + * Properties:</br> + * <ul> + * <li>{@link #getSource()} - The source info (for registered clients).</li> * + * <li>{@link #getClassName()} - The class name of the source.</li> + * <li>{@link #getPackageName()} - The package name of the source.</li> + * <li>{@link #getEventTime()} - The event time.</li> + * <li>{@link #getText()} - The text of the source.</li> + * <li>{@link #isEnabled()} - Whether the source is enabled.</li> + * <li>{@link #isPassword()} - Whether the source is password.</li> + * <li>{@link #isChecked()} - Whether the source is checked.</li> + * <li>{@link #getItemCount()} -The number of focusable items on the screen.</li> + * <li>{@link #getCurrentItemIndex()} - The currently focused item index.</li> + * </ul> * <p> * <b>View text changed</b> - represents the event of changing the text of an * {@link android.widget.EditText}. <br> * Type: {@link #TYPE_VIEW_TEXT_CHANGED} <br> - * Properties: - * {@link #getClassName()}, - * {@link #getPackageName()}, - * {@link #getEventTime()}, - * {@link #getText()}, - * {@link #isChecked()}, - * {@link #isEnabled()}, - * {@link #isPassword()}, - * {@link #getItemCount()}, - * {@link #getCurrentItemIndex()}, - * {@link #getFromIndex()}, - * {@link #getAddedCount()}, - * {@link #getRemovedCount()}, - * {@link #getBeforeText()} + * Properties:</br> + * <ul> + * <li>{@link #getSource()} - The source info (for registered clients).</li> * + * <li>{@link #getClassName()} - The class name of the source.</li> + * <li>{@link #getPackageName()} - The package name of the source.</li> + * <li>{@link #getEventTime()} - The event time.</li> + * <li>{@link #getText()} - The text of the source.</li> + * <li>{@link #isEnabled()} - Whether the source is enabled.</li> + * <li>{@link #isPassword()} - Whether the source is password.</li> + * <li>{@link #isChecked()} - Whether the source is checked.</li> + * <li>{@link #getFromIndex()} - The text change start index.</li> + * <li>{@link #getAddedCount()} - The number of added characters.</li> + * <li>{@link #getRemovedCount()} - The number of removed characters.</li> + * <li>{@link #getBeforeText()} - The text of the source before the change.</li> + * </ul> + * <p> + * <b>View text selection changed</b> - represents the event of changing the text + * selection of an {@link android.widget.EditText}.<br> + * Type: {@link #TYPE_VIEW_TEXT_SELECTION_CHANGED} <br> + * Properties:</br> + * <ul> + * <li>{@link #getSource()} - The source info (for registered clients).</li> * + * <li>{@link #getClassName()} - The class name of the source.</li> + * <li>{@link #getPackageName()} - The package name of the source.</li> + * <li>{@link #getEventTime()} - The event time.</li> + * <li>{@link #getText()} - The text of the source.</li> + * <li>{@link #isEnabled()} - Whether the source is enabled.</li> + * <li>{@link #isPassword()} - Whether the source is password.</li> + * <li>{@link #getFromIndex()} - The selection start index.</li> + * <li>{@link #getToIndex()} - The selection end index.</li> + * <li>{@link #getItemCount()} - The length of the source text.</li> + * <ul> + * <p> + * <b>View scrolled</b> - represents the event of scrolling a view. If + * the source is a descendant of {@link android.widget.AdapterView} the + * scroll is reported in terms of visible items - the first visible item, + * the last visible item, and the total items - because the the source + * is unaware if its pixel size since its adapter is responsible for + * creating views. In all other cases the scroll is reported as the current + * scroll on the X and Y axis respectively plus the height of the source in + * pixels.<br> + * Type: {@link #TYPE_VIEW_SCROLLED} <br> + * Properties:</br> + * <ul> + * <li>{@link #getSource()} - The source info (for registered clients).</li> * + * <li>{@link #getClassName()} - The class name of the source.</li> + * <li>{@link #getPackageName()} - The package name of the source.</li> + * <li>{@link #getEventTime()} - The event time.</li> + * <li>{@link #getText()} - The text of the source.</li> + * <li>{@link #isEnabled()} - Whether the source is enabled.</li> + * <li>{@link #getScrollX()} - The horizontal offset of the source + * (without descendants of AdapterView)).</li> + * <li>{@link #getScrollY()} - The vertical offset of the source + * (without descendants of AdapterView)).</li> + * <li>{@link #getFromIndex()} - The index of the first visible item of the source + * (for descendants of AdapterView).</li> + * <li>{@link #getToIndex()} - The index of the last visible item of the source + * (for descendants of AdapterView).</li> + * <li>{@link #getItemCount()} - The total items of the source (for descendants of AdapterView) + * or the height of the source in pixels (all other cases).</li> + * <ul> * <p> * <b>TRANSITION TYPES</b> <br> * <p> @@ -133,33 +189,40 @@ import java.util.List; * {@link android.widget.PopupWindow}, {@link android.view.Menu}, * {@link android.app.Dialog}, etc. <br> * Type: {@link #TYPE_WINDOW_STATE_CHANGED} <br> - * Properties: - * {@link #getClassName()}, - * {@link #getPackageName()}, - * {@link #getEventTime()}, - * {@link #getText()} + * Properties:</br> + * <ul> + * <li>{@link #getSource()} - The source info (for registered clients).</li> * + * <li>{@link #getClassName()} - The class name of the source.</li> + * <li>{@link #getPackageName()} - The package name of the source.</li> + * <li>{@link #getEventTime()} - The event time.</li> + * <li>{@link #getText()} - The text of the source.</li> + * </ul> * <p> * <b>Window content changed</b> - represents the event of change in the * content of a window. This change can be adding/removing view, changing * a view size, etc.<br> * Type: {@link #TYPE_WINDOW_CONTENT_CHANGED} <br> - * Properties: - * {@link #getClassName()}, - * {@link #getPackageName()}, - * {@link #getEventTime()}, - * {@link #getText()} + * Properties:</br> + * <ul> + * <li>{@link #getSource()} - The source info (for registered clients).</li> * + * <li>{@link #getClassName()} - The class name of the source.</li> + * <li>{@link #getPackageName()} - The package name of the source.</li> + * <li>{@link #getEventTime()} - The event time.</li> + * <ul> * <p> * <b>NOTIFICATION TYPES</b> <br> * <p> * <b>Notification state changed</b> - represents the event showing/hiding * {@link android.app.Notification}. * Type: {@link #TYPE_NOTIFICATION_STATE_CHANGED} <br> - * Properties: - * {@link #getClassName()}, - * {@link #getPackageName()}, - * {@link #getEventTime()}, - * {@link #getText()} - * {@link #getParcelableData()} + * Properties:</br> + * <ul> + * <li>{@link #getClassName()} - The class name of the source.</li> + * <li>{@link #getPackageName()} - The package name of the source.</li> + * <li>{@link #getEventTime()} - The event time.</li> + * <li>{@link #getText()} - The text of the source.</li> + * <li>{@link #getParcelableData()} - The posted {@link android.app.Notification}.</li> + * </ul> * <p> * <b>Security note</b> * <p> @@ -258,6 +321,16 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par public static final int TYPE_WINDOW_CONTENT_CHANGED = 0x00000800; /** + * Represents the event of scrolling a view. + */ + public static final int TYPE_VIEW_SCROLLED = 0x00001000; + + /** + * Represents the event of changing the selection in an {@link android.widget.EditText}. + */ + public static final int TYPE_VIEW_TEXT_SELECTION_CHANGED = 0x00002000; + + /** * Mask for {@link AccessibilityEvent} all types. * * @see #TYPE_VIEW_CLICKED @@ -564,6 +637,9 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par record.mCurrentItemIndex = parcel.readInt(); record.mItemCount = parcel.readInt(); record.mFromIndex = parcel.readInt(); + record.mToIndex = parcel.readInt(); + record.mScrollX = parcel.readInt(); + record.mScrollY = parcel.readInt(); record.mAddedCount = parcel.readInt(); record.mRemovedCount = parcel.readInt(); record.mClassName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); @@ -613,6 +689,9 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par parcel.writeInt(record.mCurrentItemIndex); parcel.writeInt(record.mItemCount); parcel.writeInt(record.mFromIndex); + parcel.writeInt(record.mToIndex); + parcel.writeInt(record.mScrollX); + parcel.writeInt(record.mScrollY); parcel.writeInt(record.mAddedCount); parcel.writeInt(record.mRemovedCount); TextUtils.writeToParcel(record.mClassName, parcel, flags); @@ -657,8 +736,12 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par builder.append("; IsPassword: " + record.isPassword()); builder.append("; IsChecked: " + record.isChecked()); builder.append("; IsFullScreen: " + record.isFullScreen()); + builder.append("; Scrollable: " + record.isScrollable()); builder.append("; BeforeText: " + record.mBeforeText); builder.append("; FromIndex: " + record.mFromIndex); + builder.append("; ToIndex: " + record.mToIndex); + builder.append("; ScrollX: " + record.mScrollX); + builder.append("; ScrollY: " + record.mScrollY); builder.append("; AddedCount: " + record.mAddedCount); builder.append("; RemovedCount: " + record.mRemovedCount); builder.append("; ParcelableData: " + record.mParcelableData); @@ -704,6 +787,10 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par return "TYPE_TOUCH_EXPLORATION_GESTURE_END"; case TYPE_WINDOW_CONTENT_CHANGED: return "TYPE_WINDOW_CONTENT_CHANGED"; + case TYPE_VIEW_TEXT_SELECTION_CHANGED: + return "TYPE_VIEW_TEXT_SELECTION_CHANGED"; + case TYPE_VIEW_SCROLLED: + return "TYPE_VIEW_SCROLLED"; default: return null; } diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 555667be1b28..dbbe7be4d533 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -83,6 +83,8 @@ public class AccessibilityNodeInfo implements Parcelable { private static final int PROPERTY_PASSWORD = 0x00000100; + private static final int PROPERTY_SCROLLABLE = 0x00000200; + // Readable representations - lazily initialized. private static SparseArray<String> sActionSymbolicNames; @@ -570,6 +572,27 @@ public class AccessibilityNodeInfo implements Parcelable { } /** + * Gets if the node is scrollable. + * + * @return True if the node is scrollable, false otherwise. + */ + public boolean isScrollable() { + return getBooleanProperty(PROPERTY_SCROLLABLE); + } + + /** + * Sets if the node is scrollable. + * + * @param scrollable True if the node is scrollable, false otherwise. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setScrollable(boolean scrollable) { + enforceNotSealed(); + setBooleanProperty(PROPERTY_SCROLLABLE, scrollable); + } + + /** * Gets the package this node comes from. * * @return The package name. @@ -1017,6 +1040,7 @@ public class AccessibilityNodeInfo implements Parcelable { builder.append("; longClickable: ").append(isLongClickable()); builder.append("; enabled: ").append(isEnabled()); builder.append("; password: ").append(isPassword()); + builder.append("; scrollable: " + isScrollable()); builder.append("; ["); diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java index 9c495e2126cb..b9815c5bbaef 100644 --- a/core/java/android/view/accessibility/AccessibilityRecord.java +++ b/core/java/android/view/accessibility/AccessibilityRecord.java @@ -40,6 +40,7 @@ public class AccessibilityRecord { private static final int PROPERTY_ENABLED = 0x00000002; private static final int PROPERTY_PASSWORD = 0x00000004; private static final int PROPERTY_FULL_SCREEN = 0x00000080; + private static final int PROPERTY_SCROLLABLE = 0x00000100; // Housekeeping private static final int MAX_POOL_SIZE = 10; @@ -54,6 +55,10 @@ public class AccessibilityRecord { int mCurrentItemIndex; int mItemCount; int mFromIndex; + int mToIndex; + int mScrollX; + int mScrollY; + int mAddedCount; int mRemovedCount; int mSourceViewId = View.NO_ID; @@ -71,7 +76,6 @@ public class AccessibilityRecord { * Hide constructor. */ AccessibilityRecord() { - } /** @@ -85,6 +89,9 @@ public class AccessibilityRecord { mCurrentItemIndex = record.mCurrentItemIndex; mItemCount = record.mItemCount; mFromIndex = record.mFromIndex; + mToIndex = record.mToIndex; + mScrollX = record.mScrollX; + mScrollY = record.mScrollY; mAddedCount = record.mAddedCount; mRemovedCount = record.mRemovedCount; mClassName = record.mClassName; @@ -246,6 +253,27 @@ public class AccessibilityRecord { } /** + * Gets if the source is scrollable. + * + * @return True if the source is scrollable, false otherwise. + */ + public boolean isScrollable() { + return getBooleanProperty(PROPERTY_SCROLLABLE); + } + + /** + * Sets if the source is scrollable. + * + * @param scrollable True if the source is scrollable, false otherwise. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setScrollable(boolean scrollable) { + enforceNotSealed(); + setBooleanProperty(PROPERTY_SCROLLABLE, scrollable); + } + + /** * Gets the number of items that can be visited. * * @return The number of items. @@ -288,18 +316,24 @@ public class AccessibilityRecord { } /** - * Gets the index of the first character of the changed sequence. + * Gets the index of the first character of the changed sequence, + * or the beginning of a text selection or the index of the first + * visible item when scrolling. * - * @return The index of the first character. + * @return The index of the first character or selection + * start or the first visible item. */ public int getFromIndex() { return mFromIndex; } /** - * Sets the index of the first character of the changed sequence. + * Sets the index of the first character of the changed sequence + * or the beginning of a text selection or the index of the first + * visible item when scrolling. * - * @param fromIndex The index of the first character. + * @param fromIndex The index of the first character or selection + * start or the first visible item. * * @throws IllegalStateException If called from an AccessibilityService. */ @@ -309,6 +343,65 @@ public class AccessibilityRecord { } /** + * Gets the index of text selection end or the index of the last + * visible item when scrolling. + * + * @return The index of selection end or last item index. + */ + public int getToIndex() { + return mToIndex; + } + + /** + * Sets the index of text selection end or the index of the last + * visible item when scrolling. + * + * @param toIndex The index of selection end or last item index. + */ + public void setToIndex(int toIndex) { + enforceNotSealed(); + mToIndex = toIndex; + } + + /** + * Gets the scroll position of the source along the X axis. + * + * @return The scroll along the X axis. + */ + public int getScrollX() { + return mScrollX; + } + + /** + * Sets the scroll position of the source along the X axis. + * + * @param scrollX The scroll along the X axis. + */ + public void setScrollX(int scrollX) { + enforceNotSealed(); + mScrollX = scrollX; + } + + /** + * Gets the scroll position of the source along the Y axis. + * + * @return The scroll along the Y axis. + */ + public int getScrollY() { + return mScrollY; + } + + /** + * Sets the scroll position of the source along the Y axis. + * + * @param scrollY The scroll along the Y axis. + */ + public void setScrollY(int scrollY) { + enforceNotSealed(); + mScrollY = scrollY; + } + + /** * Gets the number of added characters. * * @return The number of added characters. @@ -576,6 +669,9 @@ public class AccessibilityRecord { mCurrentItemIndex = INVALID_POSITION; mItemCount = 0; mFromIndex = 0; + mToIndex = 0; + mScrollX = 0; + mScrollY = 0; mAddedCount = 0; mRemovedCount = 0; mClassName = null; @@ -599,8 +695,12 @@ public class AccessibilityRecord { builder.append("; IsPassword: " + getBooleanProperty(PROPERTY_PASSWORD)); builder.append("; IsChecked: " + getBooleanProperty(PROPERTY_CHECKED)); builder.append("; IsFullScreen: " + getBooleanProperty(PROPERTY_FULL_SCREEN)); + builder.append("; Scrollable: " + getBooleanProperty(PROPERTY_SCROLLABLE)); builder.append("; BeforeText: " + mBeforeText); builder.append("; FromIndex: " + mFromIndex); + builder.append("; ToIndex: " + mToIndex); + builder.append("; ScrollX: " + mScrollX); + builder.append("; ScrollY: " + mScrollY); builder.append("; AddedCount: " + mAddedCount); builder.append("; RemovedCount: " + mRemovedCount); builder.append("; ParcelableData: " + mParcelableData); diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 3fe81498aad9..1449b18b0bf8 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -55,6 +55,8 @@ import android.view.ViewConfiguration; import android.view.ViewDebug; import android.view.ViewGroup; import android.view.ViewTreeObserver; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; @@ -1257,6 +1259,33 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these. } + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setScrollable(true); + } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setScrollable(true); + if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { + event.setFromIndex(mFirstPosition); + event.setToIndex(mFirstPosition + getChildCount()); + event.setItemCount(mItemCount); + } + } + + @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + // Do not append text content to scroll events they are fired frequently + // and the client has already received another event type with the text. + if (event.getEventType() != AccessibilityEvent.TYPE_VIEW_SCROLLED) { + super.dispatchPopulateAccessibilityEvent(event); + } + return false; + } + /** * Indicates whether the children's drawing cache is used during a scroll. * By default, the drawing cache is enabled but this will consume more memory. diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java index ce76bee52c9d..0ffd087e9820 100644 --- a/core/java/android/widget/Gallery.java +++ b/core/java/android/widget/Gallery.java @@ -34,6 +34,8 @@ import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.SoundEffectConstants; import android.view.ContextMenu.ContextMenuInfo; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.Transformation; /** @@ -344,7 +346,34 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList int getChildHeight(View child) { return child.getMeasuredHeight(); } - + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setScrollable(true); + } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setScrollable(true); + if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { + event.setFromIndex(mFirstPosition); + event.setToIndex(mFirstPosition + getChildCount()); + event.setItemCount(mItemCount); + } + } + + @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + // Do not append text content to scroll events they are fired frequently + // and the client has already received another event type with the text. + if (event.getEventType() != AccessibilityEvent.TYPE_VIEW_SCROLLED) { + super.dispatchPopulateAccessibilityEvent(event); + } + return false; + } + /** * Tracks a motion scroll. In reality, this is used to do just about any * movement to items (touch scroll, arrow-key scroll, set an item as selected). @@ -382,7 +411,9 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList mRecycler.clear(); setSelectionToCenterChild(); - + + onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these. + invalidate(); } diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java index 17b3bdad7115..7c9be1e8755e 100644 --- a/core/java/android/widget/HorizontalScrollView.java +++ b/core/java/android/widget/HorizontalScrollView.java @@ -35,6 +35,8 @@ import android.view.ViewConfiguration; import android.view.ViewDebug; import android.view.ViewGroup; import android.view.ViewParent; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.AnimationUtils; import java.util.List; @@ -692,6 +694,28 @@ public class HorizontalScrollView extends FrameLayout { awakenScrollBars(); } + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setScrollable(true); + } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setScrollable(true); + } + + @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + // Do not append text content to scroll events they are fired frequently + // and the client has already received another event type with the text. + if (event.getEventType() != AccessibilityEvent.TYPE_VIEW_SCROLLED) { + super.dispatchPopulateAccessibilityEvent(event); + } + return false; + } + private int getScrollRange() { int scrollRange = 0; if (getChildCount() > 0) { diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index 27edb885cabe..12775a49ff3a 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -36,6 +36,8 @@ import android.view.ViewConfiguration; import android.view.ViewDebug; import android.view.ViewGroup; import android.view.ViewParent; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.AnimationUtils; import java.util.List; @@ -690,6 +692,28 @@ public class ScrollView extends FrameLayout { awakenScrollBars(); } + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setScrollable(true); + } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setScrollable(true); + } + + @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + // Do not append text content to scroll events they are fired frequently + // and the client has already received another event type with the text. + if (event.getEventType() != AccessibilityEvent.TYPE_VIEW_SCROLLED) { + super.dispatchPopulateAccessibilityEvent(event); + } + return false; + } + private int getScrollRange() { int scrollRange = 0; if (getChildCount() > 0) { diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 17aea8b5d499..85e7eecfdd82 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -6834,9 +6834,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @param selEnd The new selection end location. */ protected void onSelectionChanged(int selStart, int selEnd) { - // intentionally empty + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED); } - + /** * Adds a TextWatcher to the list of those whose methods are called * whenever this TextView's text changes. @@ -7922,6 +7922,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final boolean isPassword = hasPasswordTransformationMethod(); event.setPassword(isPassword); + + if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) { + event.setFromIndex(Selection.getSelectionStart(mText)); + event.setToIndex(Selection.getSelectionEnd(mText)); + event.setItemCount(mText.length()); + } } @Override diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java index 8d75dff3168d..8eb046ebd94d 100644 --- a/core/java/com/android/internal/widget/ActionBarView.java +++ b/core/java/com/android/internal/widget/ActionBarView.java @@ -988,8 +988,8 @@ public class ActionBarView extends AbsActionBarView { int ypos = 0; switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) { case Gravity.CENTER_VERTICAL: - final int paddedTop = mTop + getPaddingTop(); - final int paddedBottom = mBottom - getPaddingBottom(); + final int paddedTop = getPaddingTop(); + final int paddedBottom = mBottom - mTop - getPaddingBottom(); ypos = ((paddedBottom - paddedTop) - customView.getMeasuredHeight()) / 2; break; case Gravity.TOP: @@ -1000,7 +1000,10 @@ public class ActionBarView extends AbsActionBarView { - bottomMargin; break; } - x += positionChild(customView, xpos, ypos, contentHeight); + final int customWidth = customView.getMeasuredWidth(); + customView.layout(xpos, ypos, xpos + customWidth, + ypos + customView.getMeasuredHeight()); + x += customWidth; } if (mProgressView != null) { diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index 1b22e2d02563..18bd754470f0 100644 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -273,7 +273,8 @@ static void Bitmap_recycle(JNIEnv* env, jobject, SkBitmap* bitmap) { // These must match the int values in Bitmap.java
enum JavaEncodeFormat {
kJPEG_JavaEncodeFormat = 0,
- kPNG_JavaEncodeFormat = 1
+ kPNG_JavaEncodeFormat = 1,
+ kWEBP_JavaEncodeFormat = 2
};
static bool Bitmap_compress(JNIEnv* env, jobject clazz, SkBitmap* bitmap,
@@ -288,6 +289,9 @@ static bool Bitmap_compress(JNIEnv* env, jobject clazz, SkBitmap* bitmap, case kPNG_JavaEncodeFormat:
fm = SkImageEncoder::kPNG_Type;
break;
+ case kWEBP_JavaEncodeFormat:
+ fm = SkImageEncoder::kWEBP_Type;
+ break;
default:
return false;
}
diff --git a/core/jni/android/graphics/SurfaceTexture.cpp b/core/jni/android/graphics/SurfaceTexture.cpp index 30ef8dfad904..dd8b3780f06c 100644 --- a/core/jni/android/graphics/SurfaceTexture.cpp +++ b/core/jni/android/graphics/SurfaceTexture.cpp @@ -91,7 +91,8 @@ public: virtual void onFrameAvailable(); private: - static JNIEnv* getJNIEnv(); + static JNIEnv* getJNIEnv(bool* needsDetach); + static void detachJNI(); jobject mWeakThiz; jclass mClazz; @@ -103,37 +104,57 @@ JNISurfaceTextureContext::JNISurfaceTextureContext(JNIEnv* env, mClazz((jclass)env->NewGlobalRef(clazz)) {} -JNIEnv* JNISurfaceTextureContext::getJNIEnv() { - JNIEnv* env; - JavaVMAttachArgs args = {JNI_VERSION_1_4, NULL, NULL}; +JNIEnv* JNISurfaceTextureContext::getJNIEnv(bool* needsDetach) { + *needsDetach = false; + JNIEnv* env = AndroidRuntime::getJNIEnv(); + if (env == NULL) { + JavaVMAttachArgs args = {JNI_VERSION_1_4, NULL, NULL}; + JavaVM* vm = AndroidRuntime::getJavaVM(); + int result = vm->AttachCurrentThread(&env, (void*) &args); + if (result != JNI_OK) { + LOGE("thread attach failed: %#x", result); + return NULL; + } + *needsDetach = true; + } + return env; +} + +void JNISurfaceTextureContext::detachJNI() { JavaVM* vm = AndroidRuntime::getJavaVM(); - int result = vm->AttachCurrentThread(&env, (void*) &args); + int result = vm->DetachCurrentThread(); if (result != JNI_OK) { - LOGE("thread attach failed: %#x", result); - return NULL; + LOGE("thread detach failed: %#x", result); } - return env; } JNISurfaceTextureContext::~JNISurfaceTextureContext() { - JNIEnv* env = getJNIEnv(); + bool needsDetach = false; + JNIEnv* env = getJNIEnv(&needsDetach); if (env != NULL) { env->DeleteGlobalRef(mWeakThiz); env->DeleteGlobalRef(mClazz); } else { LOGW("leaking JNI object references"); } + if (needsDetach) { + detachJNI(); + } } void JNISurfaceTextureContext::onFrameAvailable() { - JNIEnv *env = getJNIEnv(); + bool needsDetach = false; + JNIEnv* env = getJNIEnv(&needsDetach); if (env != NULL) { env->CallStaticVoidMethod(mClazz, fields.postEvent, mWeakThiz); } else { LOGW("onFrameAvailable event will not posted"); } + if (needsDetach) { + detachJNI(); + } } // ---------------------------------------------------------------------------- diff --git a/core/res/res/layout-land/ssl_certificate.xml b/core/res/res/layout-land/ssl_certificate.xml new file mode 100644 index 000000000000..56e4e70ca033 --- /dev/null +++ b/core/res/res/layout-land/ssl_certificate.xml @@ -0,0 +1,228 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2008 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. +--> + +<ScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" > + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" > + + <!-- Placeholder for the success message or one or more warnings --> + <LinearLayout + android:id="@+id/placeholder" + android:layout_height="wrap_content" + android:layout_width="match_parent" + android:layout_marginTop="12dip" + android:orientation="vertical" /> + + <!-- Dialog-title line separator --> + <ImageView + android:id="@+id/title_separator" + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY" + android:layout_weight="1" + android:gravity="fill_horizontal" + android:layout_marginLeft="20dip" + android:layout_marginRight="20dip" + android:layout_marginBottom="12dip" /> + + <TableLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:shrinkColumns="1" + android:orientation="vertical"> + + <!-- Issued to: --> + <TextView + android:id="@+id/issued_to_header" + android:text="@string/issued_to" + android:textStyle="bold" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_marginLeft="20dip" + android:layout_marginRight="20dip" + android:layout_marginBottom="7dip" /> + + <!-- Common name: --> + <TableRow> + <TextView + android:id="@+id/to_common_header" + android:text="@string/common_name" + android:gravity="left" + android:layout_marginLeft="20dip" /> + + <TextView + android:id="@+id/to_common" + android:gravity="left" + android:layout_marginLeft="10dip" + android:layout_marginRight="20dip" + android:layout_marginBottom="7dip" /> + </TableRow> + + <!-- Organization: --> + <TableRow> + <TextView + android:id="@+id/to_org_header" + android:text="@string/org_name" + android:gravity="left" + android:layout_marginLeft="20dip" /> + + <TextView + android:id="@+id/to_org" + android:gravity="left" + android:layout_marginLeft="10dip" + android:layout_marginRight="20dip" + android:layout_marginBottom="7dip" /> + </TableRow> + + <!-- Organizational unit: --> + <TableRow> + <TextView + android:id="@+id/to_org_unit_header" + android:text="@string/org_unit" + android:gravity="left" + android:layout_marginLeft="20dip" /> + + <TextView + android:id="@+id/to_org_unit" + android:gravity="left" + android:layout_marginLeft="10dip" + android:layout_marginRight="20dip" + android:layout_marginBottom="12dip" /> + </TableRow> + + <!-- Issued by: --> + <TextView + android:id="@+id/issued_to_header" + android:text="@string/issued_by" + android:textStyle="bold" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_marginLeft="20dip" + android:layout_marginRight="20dip" + android:layout_marginBottom="7dip" /> + + <!-- Common name: --> + <TableRow> + <TextView + android:id="@+id/by_common_header" + android:text="@string/common_name" + android:gravity="left" + android:layout_marginLeft="20dip" /> + + <TextView + android:id="@+id/by_common" + android:gravity="left" + android:layout_marginLeft="10dip" + android:layout_marginRight="20dip" + android:layout_marginBottom="7dip" /> + </TableRow> + + <!-- Organization: --> + <TableRow> + <TextView + android:id="@+id/by_org_header" + android:text="@string/org_name" + android:gravity="left" + android:layout_marginLeft="20dip" /> + + <TextView + android:id="@+id/by_org" + android:gravity="left" + android:layout_marginLeft="10dip" + android:layout_marginRight="20dip" + android:layout_marginBottom="7dip" /> + </TableRow> + + <!-- Organizational unit: --> + <TableRow> + <TextView + android:id="@+id/by_org_unit_header" + android:text="@string/org_unit" + android:gravity="left" + android:layout_marginLeft="20dip" /> + + <TextView + android:id="@+id/by_org_unit" + android:gravity="left" + android:layout_marginLeft="10dip" + android:layout_marginRight="20dip" + android:layout_marginBottom="12dip" /> + </TableRow> + + <!-- Validity Dates: --> + <TextView + android:id="@+id/validity_header" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:text="@string/validity_period" + android:textStyle="bold" + android:layout_marginLeft="20dip" + android:layout_marginRight="20dip" + android:layout_marginBottom="7dip" /> + + <!-- Issued On: --> + <TableRow> + <TextView + android:id="@+id/issued_on_header" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/issued_on" + android:gravity="left" + android:layout_marginLeft="20dip" /> + + <TextView + android:id="@+id/issued_on" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:gravity="left" + android:layout_marginLeft="10dip" + android:layout_marginRight="20dip" + android:layout_marginBottom="7dip" /> + + </TableRow> + + <!-- Expires On: --> + <TableRow> + <TextView + android:id="@+id/expires_on_header" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/expires_on" + android:gravity="left" + android:layout_marginLeft="20dip" /> + + <TextView + android:id="@+id/expires_on" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:gravity="left" + android:layout_marginLeft="10dip" + android:layout_marginRight="20dip" + android:layout_marginBottom="12dip" /> + </TableRow> + + </TableLayout> + + </LinearLayout> + +</ScrollView> diff --git a/core/res/res/layout/keyguard_screen_password_landscape.xml b/core/res/res/layout/keyguard_screen_password_landscape.xml index 937d5dcd4741..8ba08f68f17f 100644 --- a/core/res/res/layout/keyguard_screen_password_landscape.xml +++ b/core/res/res/layout/keyguard_screen_password_landscape.xml @@ -24,7 +24,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" - android:rowCount="10" + android:rowCount="11" android:id="@+id/root" android:clipChildren="false"> @@ -33,7 +33,8 @@ <com.android.internal.widget.DigitalClock android:id="@+id/time" android:layout_marginTop="16dip" - android:layout_marginBottom="8dip"> + android:layout_marginBottom="8dip" + android:layout_gravity="right"> <!-- Because we can't have multi-tone fonts, we render two TextViews, one on top of the other. Hence the redundant layout... --> @@ -67,120 +68,129 @@ android:id="@+id/date" android:layout_below="@id/time" android:layout_marginTop="6dip" + android:singleLine="true" + android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" - android:layout_gravity="left" + android:layout_gravity="right" /> <TextView android:id="@+id/alarm_status" + android:singleLine="true" + android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:drawablePadding="4dip" android:layout_marginTop="4dip" - android:layout_gravity="left" + android:layout_gravity="right" /> <TextView android:id="@+id/status1" android:layout_marginTop="4dip" + android:singleLine="true" + android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:drawablePadding="4dip" - android:layout_gravity="left" + android:layout_gravity="right" /> <TextView android:id="@+id/status2" android:layout_marginTop="4dip" + android:singleLine="true" + android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:drawablePadding="4dip" - android:layout_gravity="left" + android:layout_gravity="right" /> <TextView android:id="@+id/screenLocked" + android:singleLine="true" + android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:gravity="center" android:layout_marginTop="4dip" android:drawablePadding="4dip" - android:layout_gravity="left" + android:layout_gravity="right" /> <Space android:height="20dip"/> - <LinearLayout android:orientation="vertical" > - - <TextView - android:id="@+id/carrier" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentTop="true" - android:singleLine="true" - android:ellipsize="marquee" - android:layout_gravity="bottom|left" - android:textAppearance="?android:attr/textAppearanceMedium" - android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" - android:textColor="?android:attr/textColorSecondary" - /> - - <!-- "emergency calls only" shown when sim is missing or PUKd --> - <TextView - android:id="@+id/emergencyCallText" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentTop="true" - android:layout_marginTop="20dip" - android:singleLine="true" - android:ellipsize="marquee" - android:text="@string/emergency_calls_only" - android:textAppearance="?android:attr/textAppearanceMedium" - android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" - android:textColor="?android:attr/textColorSecondary" - /> + <TextView + android:id="@+id/carrier" + android:singleLine="true" + android:ellipsize="marquee" + android:layout_gravity="right" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" + android:textColor="?android:attr/textColorSecondary" + /> - <Button - android:id="@+id/emergencyCallButton" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:drawableLeft="@drawable/ic_emergency" - android:text="@string/lockscreen_emergency_call" - style="@style/Widget.Button.Transparent" - android:drawablePadding="8dip" - android:layout_marginRight="80dip" - android:visibility="visible" - /> - </LinearLayout> + <!-- "emergency calls only" shown when sim is missing or PUKd --> + <TextView + android:id="@+id/emergencyCallText" + android:layout_marginTop="20dip" + android:layout_gravity="right" + android:singleLine="true" + android:ellipsize="marquee" + android:text="@string/emergency_calls_only" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" + android:textColor="?android:attr/textColorSecondary" + /> - <Space android:height="20dip"/> + <Button + android:id="@+id/emergencyCallButton" + android:layout_gravity="right" + android:drawableLeft="@drawable/lockscreen_emergency_button" + android:text="@string/lockscreen_emergency_call" + style="?android:attr/buttonBarButtonStyle" + android:drawablePadding="8dip" + android:visibility="visible" + /> <!-- Column 1 --> - <Space android:width="20dip" android:layout_columnWeight="1" android:layout_rowSpan="10" /> + <Space android:layout_columnWeight="1" android:layout_rowSpan="11" /> <!-- Column 2 - password entry field and PIN keyboard --> - <EditText android:id="@+id/passwordEntry" + <LinearLayout android:layout_height="wrap_content" - android:layout_width="match_parent" - android:singleLine="true" - android:textStyle="normal" - android:inputType="textPassword" - android:gravity="center" - android:textSize="24sp" - android:textAppearance="?android:attr/textAppearanceMedium" - android:background="@drawable/lockscreen_password_field_dark" - android:textColor="?android:attr/textColorPrimary" + android:layout_width="wrap_content" + android:orientation="vertical" + android:layout_gravity="center|fill" + android:layout_rowSpan="11"> + + <EditText android:id="@+id/passwordEntry" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:singleLine="true" + android:textStyle="normal" + android:inputType="textPassword" + android:layout_gravity="center" + android:textSize="24sp" + android:minEms="8" + android:textAppearance="?android:attr/textAppearanceMedium" + android:background="@drawable/lockscreen_password_field_dark" + android:textColor="?android:attr/textColorPrimary" + /> + + <!-- Numeric keyboard --> + <com.android.internal.widget.PasswordEntryKeyboardView android:id="@+id/keyboard" + android:layout_width="250dip" + android:layout_height="100dip" + android:layout_gravity="center" + android:background="#40000000" + android:layout_marginTop="5dip" + android:keyBackground="@drawable/btn_keyboard_key_fulltrans" + android:visibility="visible" /> - <!-- Numeric keyboard --> - <com.android.internal.widget.PasswordEntryKeyboardView android:id="@+id/keyboard" - android:layout_width="300dip" - android:layout_height="300dip" - android:background="#40000000" - android:layout_marginTop="5dip" - android:keyBackground="@drawable/btn_keyboard_key_fulltrans" - android:visibility="visible" - /> + </LinearLayout> </GridLayout> diff --git a/core/res/res/layout/keyguard_screen_password_portrait.xml b/core/res/res/layout/keyguard_screen_password_portrait.xml index 30747bc72573..6953ac8f2e48 100644 --- a/core/res/res/layout/keyguard_screen_password_portrait.xml +++ b/core/res/res/layout/keyguard_screen_password_portrait.xml @@ -41,10 +41,10 @@ android:layout_marginTop="4dip" android:layout_marginRight="16dip" android:layout_gravity="right" - android:drawableLeft="@drawable/ic_emergency" + android:drawableLeft="@drawable/lockscreen_emergency_button" android:drawablePadding="8dip" android:text="@string/lockscreen_emergency_call" - style="@style/Widget.Button.Transparent" + style="?android:attr/buttonBarButtonStyle" /> <!-- bottom: password --> diff --git a/core/res/res/layout/keyguard_screen_status_land.xml b/core/res/res/layout/keyguard_screen_status_land.xml index 60dd9ffc9201..021faa375051 100644 --- a/core/res/res/layout/keyguard_screen_status_land.xml +++ b/core/res/res/layout/keyguard_screen_status_land.xml @@ -73,6 +73,8 @@ android:id="@+id/date" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:singleLine="true" + android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"/> @@ -81,6 +83,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="30dip" + android:singleLine="true" + android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"/> @@ -92,6 +96,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" + android:singleLine="true" + android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:layout_marginTop="10dip" @@ -106,6 +112,8 @@ android:layout_height="wrap_content" android:layout_marginTop="10dip" android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" + android:singleLine="true" + android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceMedium" /> diff --git a/core/res/res/layout/keyguard_screen_status_port.xml b/core/res/res/layout/keyguard_screen_status_port.xml index e72df5b63a53..f84e61c20868 100644 --- a/core/res/res/layout/keyguard_screen_status_port.xml +++ b/core/res/res/layout/keyguard_screen_status_port.xml @@ -70,6 +70,8 @@ android:id="@+id/date" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:singleLine="true" + android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"/> @@ -78,6 +80,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="16dip" + android:singleLine="true" + android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:drawablePadding="4dip"/> @@ -90,6 +94,8 @@ android:layout_height="wrap_content" android:layout_gravity="right" android:layout_marginTop="4dip" + android:singleLine="true" + android:ellipsize="marquee" android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:textAppearance="?android:attr/textAppearanceMedium" /> @@ -101,6 +107,8 @@ android:layout_height="wrap_content" android:layout_gravity="right" android:layout_alignParentTop="true" + android:singleLine="true" + android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:layout_marginTop="4dip" diff --git a/core/res/res/layout/keyguard_screen_tab_unlock.xml b/core/res/res/layout/keyguard_screen_tab_unlock.xml index 020bb27742ed..1e2abf922554 100644 --- a/core/res/res/layout/keyguard_screen_tab_unlock.xml +++ b/core/res/res/layout/keyguard_screen_tab_unlock.xml @@ -80,6 +80,8 @@ android:id="@+id/date" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:singleLine="true" + android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" /> @@ -88,6 +90,8 @@ android:id="@+id/alarm_status" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:singleLine="true" + android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:drawablePadding="4dip" @@ -105,6 +109,8 @@ android:layout_marginLeft="16dip" android:layout_marginRight="16dip" android:layout_alignParentRight="true" + android:singleLine="true" + android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:drawablePadding="4dip" @@ -119,6 +125,8 @@ android:layout_marginLeft="16dip" android:layout_marginRight="16dip" android:layout_alignParentRight="true" + android:singleLine="true" + android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:drawablePadding="4dip" @@ -133,6 +141,8 @@ android:layout_marginLeft="16dip" android:layout_marginRight="16dip" android:layout_alignParentRight="true" + android:singleLine="true" + android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:layout_marginTop="12dip" @@ -147,14 +157,13 @@ android:layout_height="wrap_content" android:layout_marginTop="4dip" android:layout_marginRight="16dip" - android:drawableLeft="@drawable/ic_emergency" + android:drawableLeft="@drawable/lockscreen_emergency_button" android:layout_alignParentRight="true" android:layout_below="@id/screenLocked" - style="@style/Widget.Button.Transparent" + style="?android:attr/buttonBarButtonStyle" android:drawablePadding="4dip" android:text="@string/lockscreen_emergency_call" android:visibility="visible" - android:background="@null" /> <RelativeLayout @@ -207,6 +216,8 @@ android:layout_marginTop="0dip" android:layout_marginRight="8dip" android:text="@string/emergency_calls_only" + android:singleLine="true" + android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:textColor="?android:attr/textColorSecondary" 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 930ac332f1bb..5588adc88a81 100644 --- a/core/res/res/layout/keyguard_screen_tab_unlock_land.xml +++ b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml @@ -68,6 +68,8 @@ android:id="@+id/date" android:layout_below="@id/time" android:layout_marginTop="6dip" + android:singleLine="true" + android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:layout_gravity="right" @@ -75,6 +77,8 @@ <TextView android:id="@+id/alarm_status" + android:singleLine="true" + android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:drawablePadding="4dip" @@ -85,6 +89,8 @@ <TextView android:id="@+id/status1" android:layout_marginTop="4dip" + android:singleLine="true" + android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:drawablePadding="4dip" @@ -94,6 +100,8 @@ <TextView android:id="@+id/status2" android:layout_marginTop="4dip" + android:singleLine="true" + android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:drawablePadding="4dip" @@ -102,6 +110,8 @@ <TextView android:id="@+id/screenLocked" + android:singleLine="true" + android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:gravity="center" @@ -113,7 +123,8 @@ <Space android:height="20dip"/> <LinearLayout android:orientation="vertical" - android:gravity="fill"> + android:layout_gravity="right" + android:gravity="fill_horizontal"> <TextView android:id="@+id/carrier" @@ -148,11 +159,10 @@ android:id="@+id/emergencyCallButton" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:drawableLeft="@drawable/ic_emergency" + android:drawableLeft="@drawable/lockscreen_emergency_button" android:text="@string/lockscreen_emergency_call" - style="@style/Widget.Button.Transparent" + style="?android:attr/buttonBarButtonStyle" android:drawablePadding="8dip" - android:layout_marginRight="80dip" android:visibility="visible" android:layout_gravity="right" /> diff --git a/core/res/res/layout/keyguard_screen_unlock_landscape.xml b/core/res/res/layout/keyguard_screen_unlock_landscape.xml index 8319b8a6d6e6..d0538dd09b63 100644 --- a/core/res/res/layout/keyguard_screen_unlock_landscape.xml +++ b/core/res/res/layout/keyguard_screen_unlock_landscape.xml @@ -26,7 +26,7 @@ android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" - android:rowCount="8"> + android:rowCount="7"> <!-- Column 0: Time, date and status --> <com.android.internal.widget.DigitalClock android:id="@+id/time" @@ -68,6 +68,8 @@ android:id="@+id/date" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:singleLine="true" + android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:layout_gravity="right" @@ -77,6 +79,8 @@ android:id="@+id/alarm_status" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:singleLine="true" + android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:layout_gravity="right" @@ -87,6 +91,8 @@ android:id="@+id/status1" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:singleLine="true" + android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:layout_gravity="right" @@ -100,31 +106,43 @@ <TextView android:id="@+id/carrier" android:layout_gravity="right" - android:textAppearance="?android:attr/textAppearanceMedium" - android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:singleLine="true" android:ellipsize="marquee" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" /> - <Button android:id="@+id/emergencyCallButton" - android:layout_gravity="right" - style="@style/Widget.Button.Transparent" - android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" - android:text="@string/lockscreen_emergency_call" - android:drawableLeft="@drawable/lockscreen_emergency_button" - android:drawablePadding="0dip" - android:background="@null" - /> + <LinearLayout + style="?android:attr/buttonBarStyle" + android:orientation="vertical" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="right"> - <Button android:id="@+id/forgotPatternButton" - android:layout_gravity="right" - style="@style/Widget.Button.Transparent" - android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" - android:text="@string/lockscreen_forgot_pattern_button_text" - android:drawableLeft="@drawable/lockscreen_forgot_password_button" - android:drawablePadding="0dip" - android:background="@null" - /> + <Button android:id="@+id/emergencyCallButton" + style="?android:attr/buttonBarButtonStyle" + android:layout_gravity="right" + android:layout_width="wrap_content" + android:layout_height="0dip" + android:layout_weight="1" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" + android:text="@string/lockscreen_emergency_call" + android:drawableLeft="@drawable/lockscreen_emergency_button" + android:drawablePadding="0dip" + /> + + <Button android:id="@+id/forgotPatternButton" + style="?android:attr/buttonBarButtonStyle" + android:layout_gravity="right" + android:layout_width="wrap_content" + android:layout_height="0dip" + android:layout_weight="1" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" + android:text="@string/lockscreen_forgot_pattern_button_text" + android:drawableLeft="@drawable/lockscreen_forgot_password_button" + android:drawablePadding="0dip" + /> + </LinearLayout> <!-- Column 1: lock pattern --> <com.android.internal.widget.LockPatternView android:id="@+id/lockPattern" @@ -134,6 +152,6 @@ android:layout_marginRight="8dip" android:layout_marginBottom="8dip" android:layout_marginLeft="8dip" - android:layout_rowSpan="8"/> + android:layout_rowSpan="7"/> </GridLayout> diff --git a/core/res/res/layout/keyguard_screen_unlock_portrait.xml b/core/res/res/layout/keyguard_screen_unlock_portrait.xml index bbe2006e47d8..774f830925f5 100644 --- a/core/res/res/layout/keyguard_screen_unlock_portrait.xml +++ b/core/res/res/layout/keyguard_screen_unlock_portrait.xml @@ -68,6 +68,8 @@ android:id="@+id/date" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:singleLine="true" + android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" /> @@ -77,6 +79,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="16dip" + android:singleLine="true" + android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:drawablePadding="4dip" @@ -89,22 +93,22 @@ android:id="@+id/status1" android:layout_gravity="right" android:layout_marginRight="@dimen/keyguard_lockscreen_status_line_font_right_margin" + android:singleLine="true" + android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:drawablePadding="4dip" - android:singleLine="true" - android:ellipsize="marquee" /> <TextView android:id="@+id/status2" android:layout_gravity="right" android:layout_marginRight="@dimen/keyguard_lockscreen_status_line_font_right_margin" + android:singleLine="true" + android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:drawablePadding="4dip" - android:singleLine="true" - android:ellipsize="marquee" android:visibility="gone" /> @@ -136,30 +140,33 @@ <!-- Footer: an emergency call button and an initially hidden "Forgot pattern" button --> <LinearLayout android:orientation="horizontal" - android:layout_gravity="fill_horizontal"> + android:layout_width="match_parent" + style="?android:attr/buttonBarStyle" + android:gravity="center" + android:weightSum="2"> <Button android:id="@+id/emergencyCallButton" - android:layout_width="wrap_content" - android:layout_height="wrap_content" android:layout_gravity="center_horizontal" - style="@style/Widget.Button.Transparent" + android:layout_width="0dip" + android:layout_height="wrap_content" + android:layout_weight="1" + style="?android:attr/buttonBarButtonStyle" android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:text="@string/lockscreen_emergency_call" android:drawableLeft="@drawable/lockscreen_emergency_button" android:drawablePadding="0dip" - android:background="@null" /> <Button android:id="@+id/forgotPatternButton" - android:layout_width="wrap_content" - android:layout_height="wrap_content" android:layout_gravity="center_horizontal" - style="@style/Widget.Button.Transparent" + android:layout_width="0dip" + android:layout_height="wrap_content" + android:layout_weight="1" + style="?android:attr/buttonBarButtonStyle" android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:text="@string/lockscreen_forgot_pattern_button_text" android:drawableLeft="@drawable/lockscreen_forgot_password_button" android:drawablePadding="0dip" - android:background="@null" /> </LinearLayout> diff --git a/core/res/res/layout/ssl_certificate.xml b/core/res/res/layout/ssl_certificate.xml new file mode 100644 index 000000000000..7206077ce6f5 --- /dev/null +++ b/core/res/res/layout/ssl_certificate.xml @@ -0,0 +1,242 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2008 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. +--> + +<ScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" > + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" > + + <!-- Placeholder for the success message or one or more warnings --> + <LinearLayout + android:id="@+id/placeholder" + android:layout_height="wrap_content" + android:layout_width="match_parent" + android:layout_marginTop="12dip" + android:orientation="vertical" /> + + <!-- Dialog-title line separator --> + <ImageView + android:id="@+id/title_separator" + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY" + android:layout_weight="1" + android:gravity="fill_horizontal" + android:layout_marginRight="20dip" + android:layout_marginLeft="20dip" + android:layout_marginBottom="12dip" /> + + <TableLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <!-- Issued to: --> + <TextView + android:id="@+id/issued_to_header" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:text="@string/issued_to" + android:textStyle="bold" + android:layout_marginLeft="20dip" + android:layout_marginRight="20dip" + android:layout_marginBottom="10dip" /> + + <!-- Common name: --> + <TextView + android:id="@+id/to_common_header" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:text="@string/common_name" + android:gravity="left" + android:layout_marginLeft="20dip" + android:layout_marginRight="20dip" /> + + <TextView + android:id="@+id/to_common" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:gravity="left" + android:layout_marginLeft="20dip" + android:layout_marginRight="20dip" + android:layout_marginBottom="10dip" /> + + <!-- Organization: --> + <TextView + android:id="@+id/to_org_header" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:text="@string/org_name" + android:gravity="left" + android:layout_marginLeft="20dip" + android:layout_marginRight="20dip" /> + + <TextView + android:id="@+id/to_org" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:gravity="left" + android:layout_marginLeft="20dip" + android:layout_marginRight="20dip" + android:layout_marginBottom="10dip" /> + + <!-- Organizational unit: --> + <TextView + android:id="@+id/to_org_unit_header" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:text="@string/org_unit" + android:gravity="left" + android:layout_marginLeft="20dip" + android:layout_marginRight="20dip" /> + + <TextView + android:id="@+id/to_org_unit" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:gravity="left" + android:layout_marginLeft="20dip" + android:layout_marginRight="20dip" + android:layout_marginBottom="12dip" /> + + <!-- Issued by: --> + <TextView + android:id="@+id/issued_to_header" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:text="@string/issued_by" + android:textStyle="bold" + android:layout_marginLeft="20dip" + android:layout_marginRight="20dip" + android:layout_marginBottom="10dip" /> + + <!-- Common name: --> + <TextView + android:id="@+id/by_common_header" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/common_name" + android:gravity="left" + android:layout_marginLeft="20dip" + android:layout_marginRight="20dip" /> + + <TextView + android:id="@+id/by_common" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:gravity="left" + android:layout_marginLeft="20dip" + android:layout_marginRight="20dip" + android:layout_marginBottom="10dip" /> + + <!-- Organization: --> + <TextView + android:id="@+id/by_org_header" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:text="@string/org_name" + android:gravity="left" + android:layout_marginLeft="20dip" + android:layout_marginRight="20dip" /> + + <TextView + android:id="@+id/by_org" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:gravity="left" + android:layout_marginLeft="20dip" + android:layout_marginRight="20dip" + android:layout_marginBottom="10dip" /> + + <!-- Organizational unit: --> + <TextView + android:id="@+id/by_org_unit_header" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:text="@string/org_unit" + android:gravity="left" + android:layout_marginLeft="20dip" + android:layout_marginRight="20dip" /> + + <TextView + android:id="@+id/by_org_unit" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:gravity="left" + android:layout_marginLeft="20dip" + android:layout_marginRight="20dip" + android:layout_marginBottom="12dip" /> + + <!-- Validity Dates: --> + <TextView + android:id="@+id/validity_header" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:text="@string/validity_period" + android:textStyle="bold" + android:layout_marginLeft="20dip" + android:layout_marginRight="20dip" + android:layout_marginBottom="10dip" /> + + <!-- Issued On: --> + <TextView + android:id="@+id/issued_on_header" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/issued_on" + android:gravity="left" + android:layout_marginLeft="20dip" + android:layout_marginRight="20dip" /> + + <TextView + android:id="@+id/issued_on" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:gravity="left" + android:layout_marginLeft="20dip" + android:layout_marginRight="20dip" + android:layout_marginBottom="10dip" /> + + <!-- Expires On: --> + <TextView + android:id="@+id/expires_on_header" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/expires_on" + android:gravity="left" + android:layout_marginLeft="20dip" + android:layout_marginRight="20dip" /> + + <TextView + android:id="@+id/expires_on" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:gravity="left" + android:layout_marginLeft="20dip" + android:layout_marginRight="20dip" + android:layout_marginBottom="12dip" /> + + </TableLayout> + + </LinearLayout> + +</ScrollView> diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml index a3267125b84c..648f6656c4c7 100644 --- a/core/res/res/values-ar/strings.xml +++ b/core/res/res/values-ar/strings.xml @@ -1106,4 +1106,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"شهادة الأمان"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"هذه الشهادة صالحة."</string> + <string name="issued_to" msgid="9032338008819841339">"تم الإصدار إلى:"</string> + <string name="common_name" msgid="5745530093500062357">"الاسم الشائع:"</string> + <string name="org_name" msgid="8868889052889991293">"المؤسسة:"</string> + <string name="org_unit" msgid="4489056376307768196">"وحدة تنظيمية:"</string> + <string name="issued_by" msgid="6959484326943152487">"تم الإصدار بواسطة:"</string> + <string name="validity_period" msgid="57988851973181309">"الصلاحية:"</string> + <string name="issued_on" msgid="2082890654801808368">"تم الإصدار في:"</string> + <string name="expires_on" msgid="8061200430557020704">"تنتهي الصلاحية في:"</string> </resources> diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml index 563380741219..6401b1c79f66 100644 --- a/core/res/res/values-bg/strings.xml +++ b/core/res/res/values-bg/strings.xml @@ -1106,4 +1106,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"Сертификат за сигурност"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"Сертификатът е валиден."</string> + <string name="issued_to" msgid="9032338008819841339">"Издаден на:"</string> + <string name="common_name" msgid="5745530093500062357">"Общо име:"</string> + <string name="org_name" msgid="8868889052889991293">"Организация:"</string> + <string name="org_unit" msgid="4489056376307768196">"Организационна единица:"</string> + <string name="issued_by" msgid="6959484326943152487">"Издаден от:"</string> + <string name="validity_period" msgid="57988851973181309">"Валидност:"</string> + <string name="issued_on" msgid="2082890654801808368">"Издаден на:"</string> + <string name="expires_on" msgid="8061200430557020704">"Изтича на:"</string> </resources> diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml index e812217c3746..a2e75d3e476e 100644 --- a/core/res/res/values-ca/strings.xml +++ b/core/res/res/values-ca/strings.xml @@ -1106,4 +1106,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"Certificat de seguretat"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"Aquest certificat és vàlid."</string> + <string name="issued_to" msgid="9032338008819841339">"Emès per a:"</string> + <string name="common_name" msgid="5745530093500062357">"Nom comú:"</string> + <string name="org_name" msgid="8868889052889991293">"Organització:"</string> + <string name="org_unit" msgid="4489056376307768196">"Unitat organitzativa:"</string> + <string name="issued_by" msgid="6959484326943152487">"Publicat per:"</string> + <string name="validity_period" msgid="57988851973181309">"Validesa:"</string> + <string name="issued_on" msgid="2082890654801808368">"Publicat el:"</string> + <string name="expires_on" msgid="8061200430557020704">"Caduca el:"</string> </resources> diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index d10e1b4654e1..ca3035d1f320 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -1106,4 +1106,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"Certifikát zabezpečení"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"Tento certifikát je platný."</string> + <string name="issued_to" msgid="9032338008819841339">"Vydáno komu:"</string> + <string name="common_name" msgid="5745530093500062357">"Běžný název:"</string> + <string name="org_name" msgid="8868889052889991293">"Organizace:"</string> + <string name="org_unit" msgid="4489056376307768196">"Organizační jednotka:"</string> + <string name="issued_by" msgid="6959484326943152487">"Vydal:"</string> + <string name="validity_period" msgid="57988851973181309">"Platnost:"</string> + <string name="issued_on" msgid="2082890654801808368">"Datum vydání:"</string> + <string name="expires_on" msgid="8061200430557020704">"Platnost vyprší:"</string> </resources> diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index a7f62ecc9015..715341615b58 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -1106,4 +1106,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"Sikkerhedscertifikat"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"Dette certifikat er gyldigt."</string> + <string name="issued_to" msgid="9032338008819841339">"Udstedt til:"</string> + <string name="common_name" msgid="5745530093500062357">"Fællesnavn:"</string> + <string name="org_name" msgid="8868889052889991293">"Organisation:"</string> + <string name="org_unit" msgid="4489056376307768196">"Organisatorisk enhed:"</string> + <string name="issued_by" msgid="6959484326943152487">"Udstedt af:"</string> + <string name="validity_period" msgid="57988851973181309">"Gyldighed:"</string> + <string name="issued_on" msgid="2082890654801808368">"Udstedt den:"</string> + <string name="expires_on" msgid="8061200430557020704">"Udløber den:"</string> </resources> diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index afa606f3c7ac..975348250c6b 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -1106,4 +1106,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"Sicherheitszertifikat"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"Dies ist ein gültiges Zertifikat."</string> + <string name="issued_to" msgid="9032338008819841339">"Ausgegeben an:"</string> + <string name="common_name" msgid="5745530093500062357">"Allgemeiner Name."</string> + <string name="org_name" msgid="8868889052889991293">"Organisation:"</string> + <string name="org_unit" msgid="4489056376307768196">"Organisationseinheit:"</string> + <string name="issued_by" msgid="6959484326943152487">"Ausgegeben von:"</string> + <string name="validity_period" msgid="57988851973181309">"Gültigkeit:"</string> + <string name="issued_on" msgid="2082890654801808368">"Ausgegeben am:"</string> + <string name="expires_on" msgid="8061200430557020704">"Läuft ab am:"</string> </resources> diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml index 65d04b48278e..81b9107ee5d0 100644 --- a/core/res/res/values-el/strings.xml +++ b/core/res/res/values-el/strings.xml @@ -1106,4 +1106,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"Πιστοποιητικό ασφαλείας"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"Αυτό το πιστοποιητικό είναι έγκυρο."</string> + <string name="issued_to" msgid="9032338008819841339">"Εκδόθηκε στις:"</string> + <string name="common_name" msgid="5745530093500062357">"Κοινό όνομα:"</string> + <string name="org_name" msgid="8868889052889991293">"Οργανισμός:"</string> + <string name="org_unit" msgid="4489056376307768196">"Μονάδα οργάνωσης:"</string> + <string name="issued_by" msgid="6959484326943152487">"Εκδόθηκε:"</string> + <string name="validity_period" msgid="57988851973181309">"Ισχύς:"</string> + <string name="issued_on" msgid="2082890654801808368">"Εκδόθηκε στις:"</string> + <string name="expires_on" msgid="8061200430557020704">"Λήγει:"</string> </resources> diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index c048b99f7e1f..7c2cc2f89168 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -1106,4 +1106,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"Certificado de seguridad"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"Este certificado es válido."</string> + <string name="issued_to" msgid="9032338008819841339">"Emitido para:"</string> + <string name="common_name" msgid="5745530093500062357">"Nombre común:"</string> + <string name="org_name" msgid="8868889052889991293">"Organización:"</string> + <string name="org_unit" msgid="4489056376307768196">"Departamento:"</string> + <string name="issued_by" msgid="6959484326943152487">"Emitido por:"</string> + <string name="validity_period" msgid="57988851973181309">"Validez:"</string> + <string name="issued_on" msgid="2082890654801808368">"Fecha de emisión:"</string> + <string name="expires_on" msgid="8061200430557020704">"Fecha de caducidad:"</string> </resources> diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml index 4ea2aa7218a5..48ad6f169484 100644 --- a/core/res/res/values-fa/strings.xml +++ b/core/res/res/values-fa/strings.xml @@ -1106,4 +1106,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"گواهی امنیتی"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"این گواهی معتبر است."</string> + <string name="issued_to" msgid="9032338008819841339">"صدور برای:"</string> + <string name="common_name" msgid="5745530093500062357">"نام عادی:"</string> + <string name="org_name" msgid="8868889052889991293">"سازمان:"</string> + <string name="org_unit" msgid="4489056376307768196">"واحد سازمانی:"</string> + <string name="issued_by" msgid="6959484326943152487">"صدور توسط:"</string> + <string name="validity_period" msgid="57988851973181309">"اعتبار:"</string> + <string name="issued_on" msgid="2082890654801808368">"صدور در:"</string> + <string name="expires_on" msgid="8061200430557020704">"تاریخ انقضا:"</string> </resources> diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml index 50c02680c445..5ddbb4554b82 100644 --- a/core/res/res/values-fi/strings.xml +++ b/core/res/res/values-fi/strings.xml @@ -1106,4 +1106,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"Suojausvarmenne"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"Varmenne on voimassa."</string> + <string name="issued_to" msgid="9032338008819841339">"Varmenteen saaja:"</string> + <string name="common_name" msgid="5745530093500062357">"Yleinen nimi:"</string> + <string name="org_name" msgid="8868889052889991293">"Organisaatio:"</string> + <string name="org_unit" msgid="4489056376307768196">"Organisaatioyksikkö:"</string> + <string name="issued_by" msgid="6959484326943152487">"Myöntäjä:"</string> + <string name="validity_period" msgid="57988851973181309">"Voimassa:"</string> + <string name="issued_on" msgid="2082890654801808368">"Myönnetty:"</string> + <string name="expires_on" msgid="8061200430557020704">"Vanhenee:"</string> </resources> diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index 8d9533820ea4..fe1dd13c5850 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -1106,4 +1106,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"Certificat de sécurité"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"Ce certificat est valide."</string> + <string name="issued_to" msgid="9032338008819841339">"Émis à :"</string> + <string name="common_name" msgid="5745530093500062357">"Nom commun :"</string> + <string name="org_name" msgid="8868889052889991293">"Organisation :"</string> + <string name="org_unit" msgid="4489056376307768196">"Unité d\'organisation :"</string> + <string name="issued_by" msgid="6959484326943152487">"Émis par :"</string> + <string name="validity_period" msgid="57988851973181309">"Validité :"</string> + <string name="issued_on" msgid="2082890654801808368">"Émis le :"</string> + <string name="expires_on" msgid="8061200430557020704">"Expire le :"</string> </resources> diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml index 71d49cc75c55..391fd08cc5bb 100644 --- a/core/res/res/values-hr/strings.xml +++ b/core/res/res/values-hr/strings.xml @@ -1106,4 +1106,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"Certifikat o sigurnosti"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"Ovaj je certifikat valjan."</string> + <string name="issued_to" msgid="9032338008819841339">"Izdano do:"</string> + <string name="common_name" msgid="5745530093500062357">"Zajednički naziv:"</string> + <string name="org_name" msgid="8868889052889991293">"Tvrtka ili ustanova:"</string> + <string name="org_unit" msgid="4489056376307768196">"Organizacijska jedinica:"</string> + <string name="issued_by" msgid="6959484326943152487">"Izdao:"</string> + <string name="validity_period" msgid="57988851973181309">"Vrijedi do:"</string> + <string name="issued_on" msgid="2082890654801808368">"Izdano dana:"</string> + <string name="expires_on" msgid="8061200430557020704">"Ističe dana:"</string> </resources> diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml index bbe635f42869..c5e16cfa7dc2 100644 --- a/core/res/res/values-hu/strings.xml +++ b/core/res/res/values-hu/strings.xml @@ -1106,4 +1106,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"Biztonsági tanúsítvány"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"A tanúsítvány érvényes."</string> + <string name="issued_to" msgid="9032338008819841339">"Kiállítva a következőnek:"</string> + <string name="common_name" msgid="5745530093500062357">"Név:"</string> + <string name="org_name" msgid="8868889052889991293">"Szervezet:"</string> + <string name="org_unit" msgid="4489056376307768196">"Szervezeti egység:"</string> + <string name="issued_by" msgid="6959484326943152487">"Kiállította:"</string> + <string name="validity_period" msgid="57988851973181309">"Érvényesség:"</string> + <string name="issued_on" msgid="2082890654801808368">"Kiállítva:"</string> + <string name="expires_on" msgid="8061200430557020704">"Lejár:"</string> </resources> diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml index 53d245a20216..4e7504a610f1 100644 --- a/core/res/res/values-in/strings.xml +++ b/core/res/res/values-in/strings.xml @@ -1106,4 +1106,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"Sertifikat keamanan"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"Sertifikat ini valid."</string> + <string name="issued_to" msgid="9032338008819841339">"Diterbitkan ke:"</string> + <string name="common_name" msgid="5745530093500062357">"Nama umum:"</string> + <string name="org_name" msgid="8868889052889991293">"Organisasi:"</string> + <string name="org_unit" msgid="4489056376307768196">"Unit organisasi:"</string> + <string name="issued_by" msgid="6959484326943152487">"Diterbitkan oleh:"</string> + <string name="validity_period" msgid="57988851973181309">"Validitas:"</string> + <string name="issued_on" msgid="2082890654801808368">"Diterbitkan pada:"</string> + <string name="expires_on" msgid="8061200430557020704">"Kedaluwarsa pada:"</string> </resources> diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml index 3c9d90901616..10d1787fc62d 100644 --- a/core/res/res/values-it/strings.xml +++ b/core/res/res/values-it/strings.xml @@ -1106,4 +1106,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"Certificato di protezione"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"Questo certificato è valido."</string> + <string name="issued_to" msgid="9032338008819841339">"Rilasciato a:"</string> + <string name="common_name" msgid="5745530093500062357">"Nome comune:"</string> + <string name="org_name" msgid="8868889052889991293">"Organizzazione:"</string> + <string name="org_unit" msgid="4489056376307768196">"Unità organizzativa:"</string> + <string name="issued_by" msgid="6959484326943152487">"Rilasciato da:"</string> + <string name="validity_period" msgid="57988851973181309">"Validità:"</string> + <string name="issued_on" msgid="2082890654801808368">"Rilasciato il:"</string> + <string name="expires_on" msgid="8061200430557020704">"Scade il:"</string> </resources> diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml index 71aa9f199193..3fce696481f4 100644 --- a/core/res/res/values-iw/strings.xml +++ b/core/res/res/values-iw/strings.xml @@ -1106,4 +1106,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"אישור אבטחה"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"אישור זה תקף."</string> + <string name="issued_to" msgid="9032338008819841339">"הופק עבור:"</string> + <string name="common_name" msgid="5745530093500062357">"שם משותף:"</string> + <string name="org_name" msgid="8868889052889991293">"ארגון:"</string> + <string name="org_unit" msgid="4489056376307768196">"יחידה ארגונית:"</string> + <string name="issued_by" msgid="6959484326943152487">"הופק על ידי:"</string> + <string name="validity_period" msgid="57988851973181309">"חוקיות:"</string> + <string name="issued_on" msgid="2082890654801808368">"הונפק בתאריך:"</string> + <string name="expires_on" msgid="8061200430557020704">"פג תוקף ב:"</string> </resources> diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml index 388b47b23d1c..9771a29459a3 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -1106,4 +1106,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"セキュリティ証明書"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"この証明書は有効です。"</string> + <string name="issued_to" msgid="9032338008819841339">"発行先:"</string> + <string name="common_name" msgid="5745530093500062357">"共通名:"</string> + <string name="org_name" msgid="8868889052889991293">"組織:"</string> + <string name="org_unit" msgid="4489056376307768196">"組織単位:"</string> + <string name="issued_by" msgid="6959484326943152487">"発行者:"</string> + <string name="validity_period" msgid="57988851973181309">"有効期間:"</string> + <string name="issued_on" msgid="2082890654801808368">"発行:"</string> + <string name="expires_on" msgid="8061200430557020704">"有効期限:"</string> </resources> diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index f7e1a757366b..a50f743df9f4 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -1106,4 +1106,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"보안 인증서"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"유효한 인증서입니다."</string> + <string name="issued_to" msgid="9032338008819841339">"발급 대상:"</string> + <string name="common_name" msgid="5745530093500062357">"일반 이름:"</string> + <string name="org_name" msgid="8868889052889991293">"조직:"</string> + <string name="org_unit" msgid="4489056376307768196">"조직 구성 단위:"</string> + <string name="issued_by" msgid="6959484326943152487">"발급 기관:"</string> + <string name="validity_period" msgid="57988851973181309">"유효성:"</string> + <string name="issued_on" msgid="2082890654801808368">"발급 날짜:"</string> + <string name="expires_on" msgid="8061200430557020704">"만료 날짜:"</string> </resources> diff --git a/core/res/res/values-land/dimens.xml b/core/res/res/values-land/dimens.xml index c500fd31870b..ec2313ce6aaf 100644 --- a/core/res/res/values-land/dimens.xml +++ b/core/res/res/values-land/dimens.xml @@ -39,6 +39,6 @@ <dimen name="action_bar_subtitle_text_size">12dp</dimen> <!-- Size of clock font in LockScreen on Unsecure unlock screen. --> - <dimen name="keyguard_lockscreen_clock_font_size">80sp</dimen> + <dimen name="keyguard_lockscreen_clock_font_size">70sp</dimen> </resources> diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml index 549641ff4cec..d99d0ec92a7b 100644 --- a/core/res/res/values-lt/strings.xml +++ b/core/res/res/values-lt/strings.xml @@ -1106,4 +1106,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"Saugos sertifikatas"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"Šis sertifikatas galioja."</string> + <string name="issued_to" msgid="9032338008819841339">"Išduota:"</string> + <string name="common_name" msgid="5745530093500062357">"Bendras pavadinimas:"</string> + <string name="org_name" msgid="8868889052889991293">"Organizacija:"</string> + <string name="org_unit" msgid="4489056376307768196">"Organizacinis vienetas:"</string> + <string name="issued_by" msgid="6959484326943152487">"Išdavė:"</string> + <string name="validity_period" msgid="57988851973181309">"Galiojimas:"</string> + <string name="issued_on" msgid="2082890654801808368">"Išduota:"</string> + <string name="expires_on" msgid="8061200430557020704">"Galiojimas baigiasi:"</string> </resources> diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml index e32df3be9b7c..553f3eed2f96 100644 --- a/core/res/res/values-lv/strings.xml +++ b/core/res/res/values-lv/strings.xml @@ -1106,4 +1106,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"Drošības sertifikāts"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"Sertifikāts ir derīgs."</string> + <string name="issued_to" msgid="9032338008819841339">"Izdots:"</string> + <string name="common_name" msgid="5745530093500062357">"Kopējais nosaukums:"</string> + <string name="org_name" msgid="8868889052889991293">"Organizācija:"</string> + <string name="org_unit" msgid="4489056376307768196">"Organizācijas vienība:"</string> + <string name="issued_by" msgid="6959484326943152487">"Izsniedzējs:"</string> + <string name="validity_period" msgid="57988851973181309">"Derīgums:"</string> + <string name="issued_on" msgid="2082890654801808368">"Izsniegšanas datums:"</string> + <string name="expires_on" msgid="8061200430557020704">"Derīguma termiņš:"</string> </resources> diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml index 15896ae6e5e2..f432672985ff 100644 --- a/core/res/res/values-ms/strings.xml +++ b/core/res/res/values-ms/strings.xml @@ -1127,4 +1127,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"Sijil keselamatan"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"Sijil ini sah."</string> + <string name="issued_to" msgid="9032338008819841339">"Dikeluarkan kepada:"</string> + <string name="common_name" msgid="5745530093500062357">"Nama biasa:"</string> + <string name="org_name" msgid="8868889052889991293">"Organisasi:"</string> + <string name="org_unit" msgid="4489056376307768196">"Unit Organisasi:"</string> + <string name="issued_by" msgid="6959484326943152487">"Dikeluarkan oleh:"</string> + <string name="validity_period" msgid="57988851973181309">"Kesahan:"</string> + <string name="issued_on" msgid="2082890654801808368">"Dikeluarkan pada:"</string> + <string name="expires_on" msgid="8061200430557020704">"Tamat tempoh pada:"</string> </resources> diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml index ca6d608c96c8..aca95fd5c229 100644 --- a/core/res/res/values-nb/strings.xml +++ b/core/res/res/values-nb/strings.xml @@ -1106,4 +1106,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"Sikkerhetssertifikat"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"Sertifikatet er gyldig."</string> + <string name="issued_to" msgid="9032338008819841339">"Utstedt til:"</string> + <string name="common_name" msgid="5745530093500062357">"Ordinært navn:"</string> + <string name="org_name" msgid="8868889052889991293">"Organisasjon:"</string> + <string name="org_unit" msgid="4489056376307768196">"Organisasjonsenhet:"</string> + <string name="issued_by" msgid="6959484326943152487">"Utstedt av:"</string> + <string name="validity_period" msgid="57988851973181309">"Gyldighet:"</string> + <string name="issued_on" msgid="2082890654801808368">"Utstedt:"</string> + <string name="expires_on" msgid="8061200430557020704">"Utløper den:"</string> </resources> diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index 8f543311167a..5fe75fc77d0e 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -1106,4 +1106,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"Beveiligingscertificaat"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"Dit certificaat is geldig."</string> + <string name="issued_to" msgid="9032338008819841339">"Uitgegeven voor:"</string> + <string name="common_name" msgid="5745530093500062357">"Algemene naam:"</string> + <string name="org_name" msgid="8868889052889991293">"Organisatie:"</string> + <string name="org_unit" msgid="4489056376307768196">"Organisatie-eenheid:"</string> + <string name="issued_by" msgid="6959484326943152487">"Uitgegeven door:"</string> + <string name="validity_period" msgid="57988851973181309">"Geldigheid:"</string> + <string name="issued_on" msgid="2082890654801808368">"Uitgegeven op:"</string> + <string name="expires_on" msgid="8061200430557020704">"Verloopt op:"</string> </resources> diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml index 10bc6149e06c..0221da7fefe2 100644 --- a/core/res/res/values-pl/strings.xml +++ b/core/res/res/values-pl/strings.xml @@ -1106,4 +1106,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"Certyfikat zabezpieczeń"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"Ten certyfikat jest prawidłowy."</string> + <string name="issued_to" msgid="9032338008819841339">"Otrzymujący:"</string> + <string name="common_name" msgid="5745530093500062357">"Nazwa pospolita:"</string> + <string name="org_name" msgid="8868889052889991293">"Organizacja:"</string> + <string name="org_unit" msgid="4489056376307768196">"Jednostka organizacyjna:"</string> + <string name="issued_by" msgid="6959484326943152487">"Wystawca:"</string> + <string name="validity_period" msgid="57988851973181309">"Poprawność:"</string> + <string name="issued_on" msgid="2082890654801808368">"Data wystawienia:"</string> + <string name="expires_on" msgid="8061200430557020704">"Wygasa:"</string> </resources> diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml index a17277e65a0a..09f08766bdb3 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -1106,4 +1106,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"Certificado de segurança"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"Este certificado é válido."</string> + <string name="issued_to" msgid="9032338008819841339">"Emitido para:"</string> + <string name="common_name" msgid="5745530093500062357">"Nome comum:"</string> + <string name="org_name" msgid="8868889052889991293">"Organização:"</string> + <string name="org_unit" msgid="4489056376307768196">"Unidade organizacional:"</string> + <string name="issued_by" msgid="6959484326943152487">"Emitido por:"</string> + <string name="validity_period" msgid="57988851973181309">"Validade:"</string> + <string name="issued_on" msgid="2082890654801808368">"Emitido em:"</string> + <string name="expires_on" msgid="8061200430557020704">"Expira em:"</string> </resources> diff --git a/core/res/res/values-rm/strings.xml b/core/res/res/values-rm/strings.xml index b1fa2179d262..c6fea8eff1fa 100644 --- a/core/res/res/values-rm/strings.xml +++ b/core/res/res/values-rm/strings.xml @@ -1223,4 +1223,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"Certificat da segirezza"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"Quest certificat è valid."</string> + <string name="issued_to" msgid="9032338008819841339">"Emess a:"</string> + <string name="common_name" msgid="5745530093500062357">"Num general:"</string> + <string name="org_name" msgid="8868889052889991293">"Organisaziun:"</string> + <string name="org_unit" msgid="4489056376307768196">"Unitad d\'organisaziun:"</string> + <string name="issued_by" msgid="6959484326943152487">"Emess da:"</string> + <string name="validity_period" msgid="57988851973181309">"Validitad:"</string> + <string name="issued_on" msgid="2082890654801808368">"Emess ils:"</string> + <string name="expires_on" msgid="8061200430557020704">"Scroda ils:"</string> </resources> diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml index 404570a59147..25fdb541284f 100644 --- a/core/res/res/values-ro/strings.xml +++ b/core/res/res/values-ro/strings.xml @@ -1106,4 +1106,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"Certificat de securitate"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"Certificatul este valid."</string> + <string name="issued_to" msgid="9032338008819841339">"Emis de:"</string> + <string name="common_name" msgid="5745530093500062357">"Nume comun:"</string> + <string name="org_name" msgid="8868889052889991293">"Organizaţie:"</string> + <string name="org_unit" msgid="4489056376307768196">"Organizaţie:"</string> + <string name="issued_by" msgid="6959484326943152487">"Emis de:"</string> + <string name="validity_period" msgid="57988851973181309">"Validitate:"</string> + <string name="issued_on" msgid="2082890654801808368">"Emis pe:"</string> + <string name="expires_on" msgid="8061200430557020704">"Expiră pe:"</string> </resources> diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index 44cc9dfbcef7..170ee8548b2a 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -1106,4 +1106,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"Сертификат безопасности"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"Этот сертификат действителен."</string> + <string name="issued_to" msgid="9032338008819841339">"Владелец сертификата:"</string> + <string name="common_name" msgid="5745530093500062357">"Стандартное имя:"</string> + <string name="org_name" msgid="8868889052889991293">"Организация:"</string> + <string name="org_unit" msgid="4489056376307768196">"Подразделение организации:"</string> + <string name="issued_by" msgid="6959484326943152487">"Выпущен:"</string> + <string name="validity_period" msgid="57988851973181309">"Срок действия:"</string> + <string name="issued_on" msgid="2082890654801808368">"Дата издания:"</string> + <string name="expires_on" msgid="8061200430557020704">"Дата окончания действия:"</string> </resources> diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml index dc962b17dd23..4ef89b7dfbfd 100644 --- a/core/res/res/values-sk/strings.xml +++ b/core/res/res/values-sk/strings.xml @@ -1106,4 +1106,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"Certifikát zabezpečenia"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"Certifikát je platný."</string> + <string name="issued_to" msgid="9032338008819841339">"Vydané pre:"</string> + <string name="common_name" msgid="5745530093500062357">"Bežný názov:"</string> + <string name="org_name" msgid="8868889052889991293">"Organizácia:"</string> + <string name="org_unit" msgid="4489056376307768196">"Organizačná jednotka:"</string> + <string name="issued_by" msgid="6959484326943152487">"Vydal:"</string> + <string name="validity_period" msgid="57988851973181309">"Platnosť:"</string> + <string name="issued_on" msgid="2082890654801808368">"Dátum vydania:"</string> + <string name="expires_on" msgid="8061200430557020704">"Platnosť vyprší:"</string> </resources> diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml index e613bf07a5d6..86546e02a1b4 100644 --- a/core/res/res/values-sl/strings.xml +++ b/core/res/res/values-sl/strings.xml @@ -1106,4 +1106,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"Varnostno potrdilo"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"Potrdilo je veljavno."</string> + <string name="issued_to" msgid="9032338008819841339">"Izdano za:"</string> + <string name="common_name" msgid="5745530093500062357">"Pogosto ime:"</string> + <string name="org_name" msgid="8868889052889991293">"Organizacija:"</string> + <string name="org_unit" msgid="4489056376307768196">"Organizacijska enota:"</string> + <string name="issued_by" msgid="6959484326943152487">"Izdal:"</string> + <string name="validity_period" msgid="57988851973181309">"Veljavnost:"</string> + <string name="issued_on" msgid="2082890654801808368">"Izdano:"</string> + <string name="expires_on" msgid="8061200430557020704">"Velja do:"</string> </resources> diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml index 455e1a9d48c6..2fd051c26d4f 100644 --- a/core/res/res/values-sr/strings.xml +++ b/core/res/res/values-sr/strings.xml @@ -1106,4 +1106,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"Безбедносни сертификат"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"Овај сертификат је важећи."</string> + <string name="issued_to" msgid="9032338008819841339">"Издато за:"</string> + <string name="common_name" msgid="5745530093500062357">"Уобичајени назив:"</string> + <string name="org_name" msgid="8868889052889991293">"Организација:"</string> + <string name="org_unit" msgid="4489056376307768196">"Организациона јединица:"</string> + <string name="issued_by" msgid="6959484326943152487">"Издавалац:"</string> + <string name="validity_period" msgid="57988851973181309">"Ваљаност:"</string> + <string name="issued_on" msgid="2082890654801808368">"Издато дана:"</string> + <string name="expires_on" msgid="8061200430557020704">"Датум истека:"</string> </resources> diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml index 22ca7e26cf2d..a17dc8d8c3c4 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -1106,4 +1106,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"Säkerhetscertifikat"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"Detta certifikat är giltigt."</string> + <string name="issued_to" msgid="9032338008819841339">"Utfärdat till:"</string> + <string name="common_name" msgid="5745530093500062357">"Nätverksnamn:"</string> + <string name="org_name" msgid="8868889052889991293">"Organisation:"</string> + <string name="org_unit" msgid="4489056376307768196">"Organisationsenhet:"</string> + <string name="issued_by" msgid="6959484326943152487">"Utfärdat av:"</string> + <string name="validity_period" msgid="57988851973181309">"Giltighet:"</string> + <string name="issued_on" msgid="2082890654801808368">"Utfärdat den:"</string> + <string name="expires_on" msgid="8061200430557020704">"Upphör att gälla:"</string> </resources> diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml index b6de800c89a4..2eb1b79165f6 100644 --- a/core/res/res/values-th/strings.xml +++ b/core/res/res/values-th/strings.xml @@ -1106,4 +1106,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"ใบรับรองความปลอดภัย"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"ใบรับรองนี้ใช้งานได้"</string> + <string name="issued_to" msgid="9032338008819841339">"ออกให้แก่:"</string> + <string name="common_name" msgid="5745530093500062357">"ชื่อทั่วไป:"</string> + <string name="org_name" msgid="8868889052889991293">"องค์กร:"</string> + <string name="org_unit" msgid="4489056376307768196">"หน่วยองค์กร:"</string> + <string name="issued_by" msgid="6959484326943152487">"ออกโดย:"</string> + <string name="validity_period" msgid="57988851973181309">"อายุการใช้งาน:"</string> + <string name="issued_on" msgid="2082890654801808368">"ออกเมื่อ:"</string> + <string name="expires_on" msgid="8061200430557020704">"หมดอายุวันที่:"</string> </resources> diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml index bba5e2f70d31..760724b8a9aa 100644 --- a/core/res/res/values-tl/strings.xml +++ b/core/res/res/values-tl/strings.xml @@ -1106,4 +1106,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"Certificate na pangseguridad"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"Wasto ang certificate na ito."</string> + <string name="issued_to" msgid="9032338008819841339">"Ibinigay kay:"</string> + <string name="common_name" msgid="5745530093500062357">"Karaniwang pangalan:"</string> + <string name="org_name" msgid="8868889052889991293">"Samahan:"</string> + <string name="org_unit" msgid="4489056376307768196">"Unit na pangsamahan:"</string> + <string name="issued_by" msgid="6959484326943152487">"Ibinigay ni:"</string> + <string name="validity_period" msgid="57988851973181309">"Pagpapatunay:"</string> + <string name="issued_on" msgid="2082890654801808368">"Ibinigay noong:"</string> + <string name="expires_on" msgid="8061200430557020704">"Mag-e-expire sa:"</string> </resources> diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index 3bce91b76e78..f9f2a1647726 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -1106,4 +1106,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"Güvenlik sertifikası"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"Bu sertifika geçerli."</string> + <string name="issued_to" msgid="9032338008819841339">"Alıcı:"</string> + <string name="common_name" msgid="5745530093500062357">"Ortak ad:"</string> + <string name="org_name" msgid="8868889052889991293">"Kuruluş:"</string> + <string name="org_unit" msgid="4489056376307768196">"Kuruluş birimi:"</string> + <string name="issued_by" msgid="6959484326943152487">"Yayınlayan:"</string> + <string name="validity_period" msgid="57988851973181309">"Geçerlilik:"</string> + <string name="issued_on" msgid="2082890654801808368">"Yayınlanma tarihi:"</string> + <string name="expires_on" msgid="8061200430557020704">"Sona erme tarihi:"</string> </resources> diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml index 3bb3788b6745..02636af03b6e 100644 --- a/core/res/res/values-uk/strings.xml +++ b/core/res/res/values-uk/strings.xml @@ -1106,4 +1106,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"Сертифікат безпеки"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"Цей сертифікат є дійсним."</string> + <string name="issued_to" msgid="9032338008819841339">"Кому видано:"</string> + <string name="common_name" msgid="5745530093500062357">"Загальне ім\'я:"</string> + <string name="org_name" msgid="8868889052889991293">"Організація:"</string> + <string name="org_unit" msgid="4489056376307768196">"Організац. підрозділ:"</string> + <string name="issued_by" msgid="6959484326943152487">"Ким видано:"</string> + <string name="validity_period" msgid="57988851973181309">"Чинність:"</string> + <string name="issued_on" msgid="2082890654801808368">"Дата видачі:"</string> + <string name="expires_on" msgid="8061200430557020704">"Діє до:"</string> </resources> diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml index db408d8e6ecd..1b1c8ba2accb 100644 --- a/core/res/res/values-vi/strings.xml +++ b/core/res/res/values-vi/strings.xml @@ -1106,4 +1106,14 @@ <skip /> <!-- no translation found for data_usage_limit_body (2182247539226163759) --> <skip /> + <string name="ssl_certificate" msgid="5226747157992111668">"Chứng chỉ bảo mật"</string> + <string name="ssl_certificate_is_valid" msgid="7096160815933145579">"Chứng chỉ này hợp lệ."</string> + <string name="issued_to" msgid="9032338008819841339">"Cấp cho:"</string> + <string name="common_name" msgid="5745530093500062357">"Tên chung:"</string> + <string name="org_name" msgid="8868889052889991293">"Tổ chức:"</string> + <string name="org_unit" msgid="4489056376307768196">"Đơn vị tổ chức:"</string> + <string name="issued_by" msgid="6959484326943152487">"Cấp bởi:"</string> + <string name="validity_period" msgid="57988851973181309">"Tính hợp lệ:"</string> + <string name="issued_on" msgid="2082890654801808368">"Cấp vào:"</string> + <string name="expires_on" msgid="8061200430557020704">"Hết hạn vào:"</string> </resources> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 58d25d6e1e3e..2f714f682d3d 100755 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -2125,6 +2125,12 @@ <!-- Specifies extra space on the bottom side of this view. This space is outside this view's bounds. --> <attr name="layout_marginBottom" format="dimension" /> + <!-- Specifies extra space on the start side of this view. + This space is outside this view's bounds. --> + <attr name="layout_marginStart" format="dimension" /> + <!-- Specifies extra space on the end side of this view. + This space is outside this view's bounds. --> + <attr name="layout_marginEnd" format="dimension" /> </declare-styleable> <!-- Use <code>input-method</code> as the root tag of the XML resource that diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index f7974e93a836..03b332ee75b6 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -235,12 +235,13 @@ <attr name="vmSafeMode" format="boolean" /> <!-- <p>Flag indicating whether the application's rendering should be hardware - accelerated if possible. This flag is turned off by default, both for - applications and activities.</p> + accelerated if possible. This flag is turned on by default for applications + that are targeting {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH} + or later.</p> <p>This flag can be set on the application and any activity declared in the manifest. When enabled for the application, each activity is automatically assumed to be hardware accelerated. This flag can be - overriden in the activity tags, either turning it off (if on for the + overridden in the activity tags, either turning it off (if on for the application) or on (if off for the application.)</p> <p>When this flag is turned on for an activity (either directly or via the application tag), every window created from the activity, including @@ -1020,7 +1021,7 @@ {@link #AndroidManifest manifest} tag. --> <declare-styleable name="AndroidManifestSupportsScreens" parent="AndroidManifest"> <!-- Starting with {@link android.os.Build.VERSION_CODES#HONEYCOMB_MR2}, - this is the new way to specify the screens an application is + this is the new way to specify the minimum screen size an application is compatible with. This attribute provides the required minimum "smallest screen width" (as per the -swNNNdp resource configuration) that the application can run on. For example, a typical phone @@ -1031,17 +1032,15 @@ or xlargeScreens attributes will be used instead. --> <attr name="requiresSmallestWidthDp" format="integer" /> <!-- Starting with {@link android.os.Build.VERSION_CODES#HONEYCOMB_MR2}, - this is the new way to specify the screens an application is + this is the new way to specify the largest screens an application is compatible with. This attribute provides the maximum "smallest screen width" (as per the -swNNNdp resource configuration) that the application is designed for. If this value is smaller than the "smallest screen width" of the device it is running on, the user - will of offered to run it in a compatibility mode that emulates a - smaller screen. Currently the compatibility mode only emulates - phone screens, so it will not be used it the application provides - a requiresSmallestWidthDp that is larger than 320. Typical values - used with this attribute are 320 for a phone screen, 600 for a - 7" tablet, and 720 for a 10" tablet. --> + is offered to run it in a compatibility mode that emulates a + smaller screen and zooms it to fit the screen. Currently the compatibility mode only + emulates phone screens with a 320dp width, so compatibility mode is not applied if the + value for compatibleWidthLimitDp is larger than 320. --> <attr name="compatibleWidthLimitDp" format="integer" /> <!-- Starting with {@link android.os.Build.VERSION_CODES#HONEYCOMB_MR2}, this is the new way to specify the screens an application is @@ -1050,9 +1049,9 @@ that the application can work well on. If this value is smaller than the "smallest screen width" of the device it is running on, the application will be forced in to screen compatibility mode with - no way for the user to turn it off. Currently the compatibility mode - only emulates phone screens, so even if this value is larger than 320 - the width the app runs in will be a 320 phone dimension. --> + no way for the user to turn it off. Currently the compatibility mode only + emulates phone screens with a 320dp width, so compatibility mode is not applied if the + value for largestWidthLimitDp is larger than 320. --> <attr name="largestWidthLimitDp" format="integer" /> <!-- Indicates whether the application supports smaller screen form-factors. A small screen is defined as one with a smaller aspect ratio than diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index a8b7b75c7fa8..0ad3184fabda 100755 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -115,6 +115,14 @@ <item>"mobile_cbs,12,0,2,60000,true"</item> </string-array> + <!-- Array of ConnectivityManager.TYPE_xxxx constants for networks that may only + be controlled by systemOrSignature apps. --> + <integer-array translatable="false" name="config_protectedNetworks"> + <item>10</item> + <item>11</item> + <item>12</item> + </integer-array> + <!-- This string array should be overridden by the device to present a list of radio attributes. This is used by the connectivity manager to decide which networks can coexist based on the hardware --> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 580c204af84e..ec191dd46969 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -1778,4 +1778,7 @@ <public type="attr" name="paddingStart"/> <public type="attr" name="paddingEnd"/> + <public type="attr" name="layout_marginStart"/> + <public type="attr" name="layout_marginEnd"/> + </resources> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index b5f408423adf..cd52a5aea219 100755 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -2959,4 +2959,26 @@ <!-- Notification body when data usage has exceeded limit threshold, and has been disabled. [CHAR LIMIT=32] --> <string name="data_usage_limit_body">tap to enable</string> + <!-- SSL Certificate dialogs --> + <!-- Title for an SSL Certificate dialog --> + <string name="ssl_certificate">Security certificate</string> + <!-- Message on an SSL Certificate dialog --> + <string name="ssl_certificate_is_valid">This certificate is valid.</string> + <!-- Label for an information field on an SSL Certificate Dialog --> + <string name="issued_to">Issued to:</string> + <!-- Label for an information field on an SSL Certificate Dialog --> + <string name="common_name">Common name:</string> + <!-- Label for an information field on an SSL Certificate Dialog --> + <string name="org_name">Organization:</string> + <!-- Label for an information field on an SSL Certificate Dialog --> + <string name="org_unit">Organizational unit:</string> + <!-- Label for an information field on an SSL Certificate Dialog --> + <string name="issued_by">Issued by:</string> + <!-- Label for an information field on an SSL Certificate Dialog --> + <string name="validity_period">Validity:</string> + <!-- Label for an information field on an SSL Certificate Dialog --> + <string name="issued_on">Issued on:</string> + <!-- Label for an information field on an SSL Certificate Dialog --> + <string name="expires_on">Expires on:</string> + </resources> diff --git a/core/tests/coretests/src/android/webkit/AccessibilityInjectorTest.java b/core/tests/coretests/src/android/webkit/AccessibilityInjectorTest.java index aedfbad59ca9..2ed7c52b69c2 100644 --- a/core/tests/coretests/src/android/webkit/AccessibilityInjectorTest.java +++ b/core/tests/coretests/src/android/webkit/AccessibilityInjectorTest.java @@ -95,7 +95,7 @@ public class AccessibilityInjectorTest private WebView mWebView; /** Used for caching the default bindings so they can be restored. */ - private String mDefaultKeyBindings; + private static String sDefaultKeyBindings; /** The received selection string for assertion checking. */ private static String sReceivedSelectionString = SELECTION_STRING_UNKNOWN; @@ -1696,7 +1696,7 @@ public class AccessibilityInjectorTest */ private void injectTestWebContentKeyBindings() { ContentResolver contentResolver = getActivity().getContentResolver(); - mDefaultKeyBindings = Settings.Secure.getString(contentResolver, + sDefaultKeyBindings = Settings.Secure.getString(contentResolver, Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS); Settings.Secure.putString(contentResolver, Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS, TEST_KEY_DINDINGS); @@ -1708,7 +1708,7 @@ public class AccessibilityInjectorTest private void restoreDefaultWebContentKeyBindings() { Settings.Secure.putString(getActivity().getContentResolver(), Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS, - mDefaultKeyBindings); + sDefaultKeyBindings); } /** diff --git a/core/tests/notificationtests/Android.mk b/core/tests/notificationtests/Android.mk new file mode 100644 index 000000000000..be2e6bfeb176 --- /dev/null +++ b/core/tests/notificationtests/Android.mk @@ -0,0 +1,16 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +# We only want this apk build for tests. +LOCAL_MODULE_TAGS := tests + +# Include all test java files. +LOCAL_SRC_FILES := \ + $(call all-java-files-under, src) + +LOCAL_JAVA_LIBRARIES := android.test.runner +LOCAL_PACKAGE_NAME := NotificationStressTests + +include $(BUILD_PACKAGE) + +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/core/tests/notificationtests/AndroidManifest.xml b/core/tests/notificationtests/AndroidManifest.xml new file mode 100644 index 000000000000..51e530ab99c5 --- /dev/null +++ b/core/tests/notificationtests/AndroidManifest.xml @@ -0,0 +1,29 @@ +<?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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.notification.tests" > + + <application > + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation + android:name="android.test.InstrumentationTestRunner" + android:targetPackage="com.android.notification.tests" + android:label="Notification Stress Tests" /> + +</manifest> diff --git a/core/tests/notificationtests/src/android/app/NotificationStressTest.java b/core/tests/notificationtests/src/android/app/NotificationStressTest.java new file mode 100644 index 000000000000..52ea1c49a39d --- /dev/null +++ b/core/tests/notificationtests/src/android/app/NotificationStressTest.java @@ -0,0 +1,92 @@ +/* + * 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 android.app; + + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.SystemClock; +import android.test.InstrumentationTestCase; +import android.test.RepetitiveTest; +import android.test.TimedTest; + +import java.util.Random; + +/** + * Test which spams notification manager with a large number of notifications, for both stress and + * performance testing. + */ +public class NotificationStressTest extends InstrumentationTestCase { + + private static final int NUM_ITERATIONS = 200; + private static final int[] ICONS = new int[] { + android.R.drawable.stat_notify_call_mute, + android.R.drawable.stat_notify_chat, + android.R.drawable.stat_notify_error, + android.R.drawable.stat_notify_missed_call, + android.R.drawable.stat_notify_more, + android.R.drawable.stat_notify_sdcard, + android.R.drawable.stat_notify_sdcard_prepare, + android.R.drawable.stat_notify_sdcard_usb, + android.R.drawable.stat_notify_sync, + android.R.drawable.stat_notify_sync_noanim, + android.R.drawable.stat_notify_voicemail, + }; + + private final Random mRandom = new Random(); + private Context mContext; + private NotificationManager mNotificationManager; + private int notifyId = 0; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mContext = getInstrumentation().getContext(); + mNotificationManager = (NotificationManager) mContext.getSystemService( + Context.NOTIFICATION_SERVICE); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + mNotificationManager.cancelAll(); + } + + @RepetitiveTest(numIterations=NUM_ITERATIONS) + public void testNotificationStress() { + // Cancel one of every five notifications to vary load on notification manager + if (notifyId % 5 == 4) { + mNotificationManager.cancel(notifyId - 4); + } + sendNotification(notifyId++, "testNotificationStressNotify"); + } + + private void sendNotification(int id, CharSequence text) { + // Create "typical" notification with random icon + Notification notification = new Notification(ICONS[mRandom.nextInt(ICONS.length)], text, + System.currentTimeMillis()); + // Fill in arbitrary content + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.google.com")); + PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0); + CharSequence title = text + " " + id; + CharSequence subtitle = String.valueOf(System.currentTimeMillis()); + notification.setLatestEventInfo(mContext, title, subtitle, pendingIntent); + mNotificationManager.notify(id, notification); + SystemClock.sleep(10); + } +} diff --git a/data/fonts/DroidSansEthiopic-Bold.ttf b/data/fonts/DroidSansEthiopic-Bold.ttf Binary files differindex 0d4500ce301c..e06cac2a53b0 100644 --- a/data/fonts/DroidSansEthiopic-Bold.ttf +++ b/data/fonts/DroidSansEthiopic-Bold.ttf diff --git a/data/fonts/DroidSansEthiopic-Regular.ttf b/data/fonts/DroidSansEthiopic-Regular.ttf Binary files differindex dd88aa1ca396..0adcbbee52a1 100644 --- a/data/fonts/DroidSansEthiopic-Regular.ttf +++ b/data/fonts/DroidSansEthiopic-Regular.ttf diff --git a/docs/html/guide/developing/device.jd b/docs/html/guide/developing/device.jd index 3127e10f3f5f..cb5a0b61e0e9 100644 --- a/docs/html/guide/developing/device.jd +++ b/docs/html/guide/developing/device.jd @@ -145,6 +145,9 @@ USB device support on Linux. The USB Vendor ID is the value given to the <td>Kyocera</td> <td><code>0482</code></td></tr> <tr> + <td>Lenevo</td> + <td><code>17EF</code></td></tr> + <tr> <td>LG</td> <td><code>1004</code></td></tr> <tr> diff --git a/docs/html/guide/guide_toc.cs b/docs/html/guide/guide_toc.cs index abea85b2f868..3ec174e6a2f4 100644 --- a/docs/html/guide/guide_toc.cs +++ b/docs/html/guide/guide_toc.cs @@ -222,7 +222,8 @@ <li><a href="<?cs var:toroot ?>guide/topics/manifest/receiver-element.html"><receiver></a></li> <li><a href="<?cs var:toroot ?>guide/topics/manifest/service-element.html"><service></a></li> <li><a href="<?cs var:toroot ?>guide/topics/manifest/supports-gl-texture-element.html"><supports-gl-texture></a></li> - <li><a href="<?cs var:toroot ?>guide/topics/manifest/supports-screens-element.html"><supports-screens></a></li> <!-- ##api level 4## --> + <li><a href="<?cs var:toroot ?>guide/topics/manifest/supports-screens-element.html"><supports-screens></a> + <span class="new">updated</span></li> <!-- ##api level 4## --> <li><a href="<?cs var:toroot ?>guide/topics/manifest/uses-configuration-element.html"><uses-configuration></a></li> <li><a href="<?cs var:toroot ?>guide/topics/manifest/uses-feature-element.html"><uses-feature></a></li> <!-- ##api level 4## --> <li><a href="<?cs var:toroot ?>guide/topics/manifest/uses-library-element.html"><uses-library></a></li> @@ -256,7 +257,7 @@ <div><a href="<?cs var:toroot ?>guide/topics/renderscript/index.html"> <span class="en">RenderScript</span> </a> - <span class="new-child">new!</span></div> + <span class="new">new!</span></div> <ul> <li><a href="<?cs var:toroot ?>guide/topics/renderscript/graphics.html"> <span class="en">3D Graphics</span> diff --git a/docs/html/guide/topics/manifest/supports-screens-element.jd b/docs/html/guide/topics/manifest/supports-screens-element.jd index ee99a3706349..605a2bb482e1 100644 --- a/docs/html/guide/topics/manifest/supports-screens-element.jd +++ b/docs/html/guide/topics/manifest/supports-screens-element.jd @@ -8,12 +8,15 @@ parent.link=manifest-intro.html <dt>syntax:</dt> <dd> <pre class="stx"> -<supports-screens android:<a href="#resizeable">resizeable</a>=["true" | "false"] - android:<a href="#small">smallScreens</a>=["true" | "false"] - android:<a href="#normal">normalScreens</a>=["true" | "false"] - android:<a href="#large">largeScreens</a>=["true" | "false"] +<supports-screens android:<a href="#requiresSmallest">requiresSmallestWidthDp</a>="<em>integer</em>" + android:<a href="#compatibleWidth">compatibleWidthLimitDp</a>="<em>integer</em>" + android:<a href="#largestWidth">largestWidthLimitDp</a>="<em>integer</em>" + android:<a href="#resizeable">resizeable</a>=["true"| "false"] + android:<a href="#small">smallScreens</a>=["true" | "false"] + android:<a href="#normal">normalScreens</a>=["true" | "false"] + android:<a href="#large">largeScreens</a>=["true" | "false"] android:<a href="#xlarge">xlargeScreens</a>=["true" | "false"] - android:<a href="#any">anyDensity</a>=["true" | "false"] /> + android:<a href="#any">anyDensity</a>=["true" | "false"] /> </pre> </dd> @@ -21,36 +24,37 @@ parent.link=manifest-intro.html <dd><code><a href="{@docRoot}guide/topics/manifest/manifest-element.html"><manifest></a></code></dd> <dt>description:</dt> -<dd>Lets you specify the screen dimensions the -application supports. By default, a modern application (using API Level 4 or higher) supports all -screen sizes; older applications are assumed to support only the "normal" screen size. Screen -size is determined as the available pixels to an application after density scaling has been -applied. (Note that screen size is a separate axis from screen density.) - -<p>An application "supports" a given screen size if it fills the entire screen and works as -expected. By default, the system will resize your application to fill the screen, if you have set +<dd>Lets you specify the screen sizes your application supports and enable screen +compatibility mode for screens larger than what your application supports. By default, a modern +application (using API Level 4 or higher) supports all screen sizes; older applications are assumed +to support only the "normal" screen size. Screen size is determined by the number of pixels on the +screen after the system accounts for screen density scaling. + +<p>An application "supports" a given screen size if it resizes properly to fill the entire screen. +By default, the system resizes your application UI to fill the screen if you have set either <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code minSdkVersion}</a> or <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code -targetSdkVersion}</a> to {@code "4"} or higher. Resizing works well for most applications and -you don't have to do any extra work to make your application work on larger screens.</p> +targetSdkVersion}</a> to {@code "4"} or higher. Normal resizing works well for most applications and +you don't have to do any extra work to make your application work on screens larger than a +handset device.</p> -<p>In addition to allowing the system to resize your application, you can add additional support -for different screen sizes by providing <a +<p>In addition to allowing the system to resize your application to fit the current screen, you can +optimize your UI for different screen sizes by providing <a href="{@docRoot}guide/topics/resources/providing-resources.html#AlternativeResources">alternative layout resources</a> for different sizes. For instance, you might want to modify the layout of an activity when it is on a tablet or similar device that has an <em>xlarge</em> screen.</p> -<p>If your application does not support <em>large</em> or <em>xlarge</em> screens, then you should -declare that it is not resizeable by setting <a href="#resizeable">{@code android:resizeable}</a> to -{@code "false"}, so that the system will not resize your application on larger screens.</p> - -<p>If your application does not support <em>small</em> screens, then -there isn't much the system can do to make the application work well on a smaller screen, so -external services (such as Android Market) should not allow users to install the application on such -screens.</p> - - -<p>For more information, see +<p>However, if your application does not work well when resized to fit different screen sizes, +you can use the attributes of the {@code <supports-screens>} element to control whether +your application should be distributed to smaller screens or have its UI scaled up to fit larger +screens using the system's screen compatibility mode. When you have not designed for larger screen +sizes and the normal resizing does not achieve the appropriate results, <em>screen compatibility +mode</em> will scale your UI by emulating a <em>normal</em> size screen and then zooming in on it so +that it fills the entire screen—thus achieving the same layout as a normal handset device on +the large screen (but this usually causes pixelation and blurring of your UI).</p> + +<p>For more information about how to properly support different screen sizes so that you can avoid +using screen compatibility mode, read <a href="{@docRoot}guide/practices/screens_support.html">Supporting Multiple Screens</a>.</p> @@ -59,6 +63,74 @@ screens.</p> <dd> <dl class="attr"> + <dt id="requiresSmallest">{@code android:requiresSmallestWidthDp}</dt> + <dd>This attribute specifies the minimum "smallest screen width" with which your +application is compatible. In order for a device to be considered compatible with your +application, the shortest side of the available screen space must be equal to or greater than this +value. +<p>The width against which your value is compared takes into account screen decorations and system +UI. For example, if the device has some persistent UI elements on the left or right edge of the +display, the system declares the device's available width as one that is smaller than the actual +screen size, accounting for these UI elements because those are screen pixels not available for your +UI. Thus, the value you use should be the actual smallest width required by your layout.</p> +<p>If your application properly resizes for smaller screen sizes (down to the +<em>small</em> size or a minimum width of 320dp), you do +not need to use this attribute. Otherwise, you should use a value for this attribute that +matches the smallest value used by your application for the <a +href="{@docRoot}guide/topics/resources/providing-resources.html#SmallestScreenWidthQualifier"> +smallest screen width qualifier</a> ({@code sw<N>dp}).</p> + +<p>For example, a typical handset screen has a minimum width of 320dp, a 7" tablet has a minimum +width of 600dp, and a 10" tablet has a minimum width of 720dp. If the smallest available screen +width on a device is less than the value you supply here, then the application is considered +incompatible with that +device. External services such as Android Market use this to determine whether a device +is compatible with your application and prevent incompatible devices from installing it.</p> +<p>Beginning with Android 3.2 (API level 13), using this attribute is the preferred way to +specify the minimum screen size your application requires, instead of using the other attributes +for small, normal, large, and xlarge screens. The advantage of using this attribute is that you +have more control over exactly how much screen space your application needs at a minimum in order +to properly display its UI, rather than relying on the generalized size groups.</p> +<p>This attribute has no default value. If this attribute is not specified, then any of the old +<code><a href="#small">smallScreens</a></code>, <code><a href="#normal">normalScreens</a></code>, +<code><a href="#large">largeScreens</a></code>, or <code><a href="#xlarge">xlargeScreens</a></code> +attributes are used instead to determine the smallest screen required.</p> + <p>This attribute was introduced in API level 13.</p> + </dd> + + <dt id="compatibleWidth">{@code android:compatibleWidthLimitDp}</dt> + <dd>This attribute allows you to enable screen compatibility mode as a user-optional feature by +specifying the maximum "smallest screen width" for which your application is designed. If the value +you supply here is less than the shortest side of the available screen space, users can still +install your application, but are offered to run it in screen compatibility mode. By default, screen +compatibility mode is disabled and your layout is resized to fit the screen as usual, but a +button is available in the system bar that allows the user to toggle screen compatibility mode on +and off. + <p>If your application is compatible with all screen sizes and its layout properly resizes, you do +not need to use this attribute.</p> + <p class="note"><strong>Note:</strong> Currently, screen compatibility mode only emulates handset +screens with a 320dp width, so screen compatibility mode is not applied if your value for {@code +android:compatibleWidthLimitDp} is larger than 320.</p> + <p>This attribute was introduced in API level 13.</p> + </dd> + + <dt id="largestWidth">{@code android:largestWidthLimitDp}</dt> + <dd>This attribute allows you to force enable screen compatibility mode by specifying the maximum +"smallest screen width" for which your application is designed. If the value you supply here is less +than the shortest side of the available screen space, the application runs in screen +compatibility mode with no way for the user to disable it. + <p>If your application is compatible with all screen sizes and its layout properly resizes, you do +not need to use this attribute. Otherwise, you should first consider using the <a +href="#compatibleWidth">{@code android:compatibleWidthLimitDp}</a> attribute. You should use the +{@code android:largestWidthLimitDp} attribute only when your application is functionally broken when +resized for larger screens and screen compatibility mode is the only way that users should use +your application.</p> + <p class="note"><strong>Note:</strong> Currently, screen compatibility mode only emulates handset +screens with a 320dp width, so screen compatibility mode is not applied if your value for {@code +android:largestWidthLimitDp} is larger than 320.</p> + <p>This attribute was introduced in API level 13.</p> + </dd> + <dt><a name="resizeable"></a>{@code android:resizeable}</dt> <dd>Indicates whether the application is resizeable for different screen sizes. This attribute is true, by default, if you have set either <a @@ -75,6 +147,7 @@ when resized.</p> application does not work well on larger screens, follow the guide to <a href="{@docRoot}guide/practices/screens_support.html">Supporting Multiple Screens</a> to enable additional screen support.</p> + <p><strong>This attribute is deprecated</strong> as of API level 13.</p> </dd> @@ -91,6 +164,7 @@ href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code targe {@code "4"} or higher, the default value for this is {@code "true"}, any value less than {@code "4"} results in this set to {@code "false"}. + <p><strong>This attribute is deprecated</strong> as of API level 13.</p> </dd> <dt><a name="normal"></a>{@code android:normalScreens}</dt> @@ -99,12 +173,13 @@ the default value for this is {@code "true"}, any value less than {@code "4"} re screen, but WQVGA low density and WVGA high density are also considered to be normal. This attribute is "true" by default, and applications currently should leave it that way. + <p><strong>This attribute is deprecated</strong> as of API level 13.</p> </dd> <dt><a name="large"></a>{@code android:largeScreens}</dt> <dd>Indicates whether the application supports larger screen form-factors. A large screen is defined as a screen that is significantly larger - than a "normal" phone screen, and thus might require some special care + than a "normal" handset screen, and thus might require some special care on the application's part to make good use of it, though it may rely on resizing by the system to fill the screen. If the application has set either <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code minSdkVersion}</a> or <a @@ -112,6 +187,7 @@ href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code targe {@code "4"} or higher, the default value for this is {@code "true"}, any value less than {@code "4"} results in this set to {@code "false"}. + <p><strong>This attribute is deprecated</strong> as of API level 13.</p> </dd> <dt><a name="xlarge"></a>{@code android:xlargeScreens}</dt> @@ -125,7 +201,8 @@ href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code targe {@code "4"} or higher, the default value for this is {@code "true"}, any value less than {@code "4"} results in this set to {@code "false"}. - <p>This attribute was introduced in API Level 9.</p> + <p>This attribute was introduced in API level 9.</p> + <p><strong>This attribute is deprecated</strong> as of API level 13.</p> </dd> <dt><a name="any"></a>{@code android:anyDensity}</dt> @@ -142,6 +219,7 @@ the default value for this is {@code "true"}. Otherwise, it is {@code "false"}. down application assets by a factor of 0.75 (low dpi screens) or scale them up by a factor of 1.5 (high dpi screens), when you don't provide alternative resources for a specifc screen density. The screen density is expressed as dots-per-inch (dpi).</p> + <p><strong>This attribute is deprecated</strong> as of API level 13.</p> </dd> diff --git a/docs/html/guide/topics/resources/providing-resources.jd b/docs/html/guide/topics/resources/providing-resources.jd index a996ccc08d76..ea778c1deb6e 100644 --- a/docs/html/guide/topics/resources/providing-resources.jd +++ b/docs/html/guide/topics/resources/providing-resources.jd @@ -335,80 +335,101 @@ indicates the current locale.</p> </tr> <tr id="SmallestScreenWidthQualifier"> <td>Smallest screen width</td> - <td>Examples:<br/> + <td><code>sw<N>dp</code><br/><br/> + Examples:<br/> <code>sw320dp</code><br/> <code>sw600dp</code><br/> <code>sw720dp</code><br/> etc. </td> <td> - <p>Specifies a minimum "smallest screen width," in "dp" units, at which the resource - should be used. This configuration value represents the base screen size - of the device, regardless of the orientation of the display. It is based - on the smallest width the application will have in which to perform its - UI layout (in dp units) regardless of the orientation of the screen. The - value here takes into account screen decorations so if the device has some - persistent UI elements on the left or right edge of the display it must - present a value here that is smaller than the real screen size, accounting - for these UI elements reducing the application's available space.</p> - <p>Some values you may use here for common screen sizes:</p> + <p>Specifies the "smallest width" in {@code dp} units that must be available to your + application in order for the resources to be used, regardless of the screen's current + orientation. For example, if your layout requires that its shortest side be at least 600 + dp in length at all times, then you can use this to create the layout resources, {@code + res/layout-sw600dp/}, and the system will use them only when the shortest side of + available screen space it at least 600dp.</p> + <p>The width against which the system compares your value takes into account screen + decorations and system UI. For example, if the device has some persistent UI elements on the + left or right edge of the display, the system declares its own available width as one that + is smaller than the actual screen size, accounting for these UI elements because those are + screen pixels not available for your UI. Thus, the value you use should be the actual + smallest width required by your layout.</p> + <p>Some values you might use here for common screen sizes:</p> <ul> - <li>240x320 ldpi (QVGA phone): 320 - <li>320x480 mdpi (phone): 320 - <li>480x800 hdpi (high density phone): 320 - <li>480x800 mdpi (tablet/phone): 480 - <li>600x1024 mdpi (7" tablet): 600 - <li>720x1280 mdpi (10" tablet): 720 + <li>320, for devices with screen configurations such as: + <ul> + <li>240x320 ldpi (QVGA handset)</li> + <li>320x480 mdpi (handset)</li> + <li>480x800 hdpi (high density handset)</li> + </ul> + </li> + <li>480, for screens such as 480x800 mdpi (tablet/handset).</li> + <li>600, for screens such as 600x1024 mdpi (7" tablet).</li> + <li>720, for screens such as 720x1280 mdpi (10" tablet).</li> </ul> - <p><em>Added in API Level 13.</em></p> - <p>Also see the {@link android.content.res.Configuration#smallestScreenWidthDp} - configuration field, which holds the current smallest screen width.</p> + <p>When your application provides multiple resource directories with different values for + this qualifier, the system uses the one closest to (without exceeding) the smallest width + for the available space. </p> + <p><em>Added in API level 13.</em></p> + <p>Also see the <a + href="{@docRoot}guide/topics/manifest/supports-screens-element.html#requiresSmallest">{@code + android:requiresSmallestWidthDp}</a> attribute, which declares the smallest available width + with which your application is compatible, and the {@link + android.content.res.Configuration#smallestScreenWidthDp} configuration field, which holds + the current smallest screen width for the device.</p> </td> </tr> <tr id="ScreenWidthQualifier"> <td>Screen width</td> - <td>Examples:<br/> + <td><code>w<N>dp</code><br/><br/> + Examples:<br/> <code>w720dp</code><br/> <code>w1024dp</code><br/> etc. </td> <td> - <p>Specifies a minimum screen width, in "dp" units, at which the resource - should be used. This configuration value will change when the orientation - changes between landscape and portrait to match the current actual width. - When multiple screen width configurations are available, the closest to - the current screen width will be used. The - value here takes into account screen decorations so if the device has some - persistent UI elements on the left or right edge of the display it must - present a value here that is smaller than the real screen size, accounting - for these UI elements reducing the application's available space.</p> - <p><em>Added in API Level 13.</em></p> + <p>Specifies a minimum screen width, in {@code dp} units at which the resource + should be used—defined by the <code><N></code> value. This + configuration value will change when the orientation + changes between landscape and portrait to match the current actual width.</p> + <p>When your application provides multiple resource directories with different values + for this configuration, the system uses the one closest to (without exceeding) + the device's current screen width. The + value here takes into account screen decorations, so if the device has some + persistent UI elements on the left or right edge of the display, it + uses a value for the width that is smaller than the real screen size, accounting + for these UI elements and reducing the application's available space.</p> + <p><em>Added in API level 13.</em></p> <p>Also see the {@link android.content.res.Configuration#screenWidthDp} configuration field, which holds the current screen width.</p> </td> </tr> <tr id="ScreenHeightQualifier"> <td>Screen height</td> - <td>Examples:<br/> + <td><code>h<N>dp</code><br/><br/> + Examples:<br/> <code>h720dp</code><br/> <code>h1024dp</code><br/> etc. </td> <td> - <p>Specifies a minimum screen height, in "dp" units, at which the resource - should be used. This configuration value will change when the orientation - changes between landscape and portrait to match the current actual height. - When multiple screen height configurations are available, the closest to - the current screen height will be used. The - value here takes into account screen decorations so if the device has some - persistent UI elements on the left or right edge of the display it must - present a value here that is smaller than the real screen size, accounting - for these UI elements reducing the application's available space. Screen + <p>Specifies a minimum screen height, in "dp" units at which the resource + should be used—defined by the <code><N></code> value. This + configuration value will change when the orientation + changes between landscape and portrait to match the current actual height.</p> + <p>When your application provides multiple resource directories with different values + for this configuration, the system uses the one closest to (without exceeding) + the device's current screen height. The + value here takes into account screen decorations, so if the device has some + persistent UI elements on the top or bottom edge of the display, it uses + a value for the height that is smaller than the real screen size, accounting + for these UI elements and reducing the application's available space. Screen decorations that are not fixed (such as a phone status bar that can be hidden when full screen) are <em>not</em> accounted for here, nor are - window decorations like title bar, so applications must be prepared to + window decorations like the title bar or action bar, so applications must be prepared to deal with a somewhat smaller space than they specify. - <p><em>Added in API Level 13.</em></p> + <p><em>Added in API level 13.</em></p> <p>Also see the {@link android.content.res.Configuration#screenHeightDp} configuration field, which holds the current screen width.</p> </td> @@ -444,9 +465,9 @@ indicates the current locale.</p> medium-density HVGA screen. The minimum layout size for this screen configuration is approximately 720x960 dp units. In most cases, devices with extra large screens would be too large to carry in a pocket and would most likely - be tablet-style devices. <em>Added in API Level 9.</em></li> + be tablet-style devices. <em>Added in API level 9.</em></li> </ul> - <p><em>Added in API Level 4.</em></p> + <p><em>Added in API level 4.</em></p> <p>See <a href="{@docRoot}guide/practices/screens_support.html">Supporting Multiple Screens</a> for more information.</p> <p>Also see the {@link android.content.res.Configuration#screenLayout} configuration field, @@ -465,7 +486,7 @@ or large.</p> <li>{@code long}: Long screens, such as WQVGA, WVGA, FWVGA</li> <li>{@code notlong}: Not long screens, such as QVGA, HVGA, and VGA</li> </ul> - <p><em>Added in API Level 4.</em></p> + <p><em>Added in API level 4.</em></p> <p>This is based purely on the aspect ratio of the screen (a "long" screen is wider). This is not related to the screen orientation.</p> <p>Also see the {@link android.content.res.Configuration#screenLayout} configuration field, @@ -503,7 +524,7 @@ which indicates the current device orientation.</p> <li>{@code car}: Device is in a car dock</li> <li>{@code desk}: Device is in a desk dock</li> </ul> - <p><em>Added in API Level 8.</em></p> + <p><em>Added in API level 8.</em></p> <p>This can change during the life of your application if the user places the device in a dock. You can enable or disable this mode using {@link android.app.UiModeManager}. See <a href="runtime-changes.html">Handling Runtime Changes</a> for @@ -521,7 +542,7 @@ information about how this affects your application during runtime.</p> <li>{@code night}: Night time</li> <li>{@code notnight}: Day time</li> </ul> - <p><em>Added in API Level 8.</em></p> + <p><em>Added in API level 8.</em></p> <p>This can change during the life of your application if night mode is left in auto mode (default), in which case the mode changes based on the time of day. You can enable or disable this mode using {@link android.app.UiModeManager}. See <a @@ -549,7 +570,7 @@ Level 8</em></li> <li>{@code nodpi}: This can be used for bitmap resources that you do not want to be scaled to match the device density.</li> </ul> - <p><em>Added in API Level 4.</em></p> + <p><em>Added in API level 4.</em></p> <p>There is thus a 3:4:6:8 scaling ratio between the four densities, so a 9x9 bitmap in ldpi is 12x12 in mdpi, 18x18 in hdpi and 24x24 in xhdpi.</p> <p>When Android selects which resource files to use, @@ -689,17 +710,17 @@ orientation" described above.</p> </tr> --> <tr id="VersionQualifier"> - <td>Platform Version (API Level)</td> + <td>Platform Version (API level)</td> <td>Examples:<br/> <code>v3</code><br/> <code>v4</code><br/> <code>v7</code><br/> etc.</td> <td> - <p>The API Level supported by the device. For example, <code>v1</code> for API Level -1 (devices with Android 1.0 or higher) and <code>v4</code> for API Level 4 (devices with Android + <p>The API level supported by the device. For example, <code>v1</code> for API level +1 (devices with Android 1.0 or higher) and <code>v4</code> for API level 4 (devices with Android 1.6 or higher). See the <a -href="{@docRoot}guide/appendix/api-levels.html">Android API Levels</a> document for more information +href="{@docRoot}guide/appendix/api-levels.html">Android API levels</a> document for more information about these values.</p> <p class="caution"><strong>Caution:</strong> Android 1.5 and 1.6 only match resources with this qualifier when it exactly matches the platform version. See the section below about <a @@ -863,7 +884,7 @@ cannot use the resources named with the new qualifier. For example, if your <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code minSdkVersion}</a> is set to 4, and you qualify all of your drawable resources using <a href="#NightQualifier">night mode</a> ({@code night} or {@code notnight}, which were added in API -Level 8), then an API Level 4 device cannot access your drawable resources and will crash. In this +Level 8), then an API level 4 device cannot access your drawable resources and will crash. In this case, you probably want {@code notnight} to be your default resources, so you should exclude that qualifier so your drawable resources are in either {@code drawable/} or {@code drawable-night/}.</p> @@ -896,7 +917,7 @@ compatibility for these versions.</p> <dd>{@code long} and {@code notlong}</dd> </dl> -<p>These configuration qualifiers were introduced in Android 1.6, so Android 1.5 (API Level 3) and +<p>These configuration qualifiers were introduced in Android 1.6, so Android 1.5 (API level 3) and lower does not support them. If you use these configuration qualifiers and do not provide corresponding default resources, then an Android 1.5 device might use any one of the resource directories named with the above screen configuration qualifiers, because it ignores these @@ -926,9 +947,9 @@ is r6 or greater. <p>You need SDK Tools, Revision 6 (or greater), because it includes a new packaging tool that automatically applies an appropriate <a href="#VersionQualifier">version qualifier</a> to any resource directory named with a qualifier that does not exist in Android 1.0. For example, because -the density qualifier was introduced in Android 1.6 (API Level 4), when the packaging tool +the density qualifier was introduced in Android 1.6 (API level 4), when the packaging tool encounters a resource directory using the density qualifier, it adds {@code v4} to the directory -name to ensure that older versions do not use those resources (only API Level 4 and higher support +name to ensure that older versions do not use those resources (only API level 4 and higher support that qualifier). Thus, by putting your medium-density resources in a directory <em>without</em> the {@code mdpi} qualifier, they are still accessible by Android 1.5, and any device that supports the density qualifer and has a medium-density screen also uses the default resources (which are mdpi) @@ -937,7 +958,7 @@ resources).</p> </li> </ol> -<p class="note"><strong>Note:</strong> Later versions of Android, such as API Level 8, +<p class="note"><strong>Note:</strong> Later versions of Android, such as API level 8, introduce other configuration qualifiers that older version do not support. To provide the best compatibility, you should always include a set of default resources for each type of resource that your application uses, as discussed above to provide the best device compatibility.</p> @@ -1068,7 +1089,7 @@ href="accessing-resources.html">Accessing Resources</a>.</p> <p>The correct behavior is for the system to match resources marked with a <a href="#VersionQualifier">version qualifier</a> equal -to or less than the platform version on the device, but on Android 1.5 and 1.6, (API Level 3 and 4), +to or less than the platform version on the device, but on Android 1.5 and 1.6, (API level 3 and 4), there is a bug that causes the system to match resources marked with the version qualifier only when it exactly matches the version on the device.</p> diff --git a/docs/html/guide/topics/search/index.jd b/docs/html/guide/topics/search/index.jd index 7ac5ff1455bb..218511b2a8e4 100644 --- a/docs/html/guide/topics/search/index.jd +++ b/docs/html/guide/topics/search/index.jd @@ -52,7 +52,13 @@ example of the search dialog with optional search suggestions.</p> <p class="note"><strong>Note</strong>: The search framework does <em>not</em> provide APIs to search your data. To perform a search, you need to use APIs appropriate for your data. For example, if your data is stored in an SQLite database, you should use the {@link android.database.sqlite} -APIs to perform searches.</p> +APIs to perform searches. +<br/><br/> +Also, there is no guarantee that every device provides a dedicated SEARCH button to invoke the +search interface in your application. When using the search dialog or a custom interface, you +must always provide a search button in your UI that activates the search interface. For more +information, see <a href="search-dialog.html#InvokingTheSearchDialog">Invoking the search +dialog</a>.</p> <p>The following documents show you how to use Android's framework to implement search:</p> diff --git a/docs/html/guide/topics/search/search-dialog.jd b/docs/html/guide/topics/search/search-dialog.jd index af6c8f2a82da..d869a44ba67a 100644 --- a/docs/html/guide/topics/search/search-dialog.jd +++ b/docs/html/guide/topics/search/search-dialog.jd @@ -17,29 +17,30 @@ access</li> <h2>In this document</h2> <ol> -<li><a href="#TheBasics">The Basics</a></li> -<li><a href="#SearchableConfiguration">Creating a Searchable Configuration</a></li> -<li><a href="#SearchableActivity">Creating a Searchable Activity</a> - <ol> - <li><a href="#DeclaringSearchableActivity">Declaring a searchable activity</a></li> - <li><a href="#EnableSearch">Enabling the search dialog and search widget</a></li> - <li><a href="#PerformingSearch">Performing a search</a></li> - </ol> -</li> -<li><a href="#UsingTheSearchDialog">Using the Search Dialog</a> - <ol> - <li><a href="#LifeCycle">The impact of the search dialog on your activity lifecycle</a></li> - <li><a href="#SearchContextData">Passing search context data</a></li> - </ol> -</li> -<li><a href="#UsingSearchWidget">Using the Search Widget</a> - <ol> - <li><a href="#ConfiguringWidget">Configuring the search widget</a></li> - <li><a href="#WidgetFeatures">Other search widget features</a></li> - </ol> -</li> -<li><a href="#VoiceSearch">Adding Voice Search</a></li> -<li><a href="#SearchSuggestions">Adding Search Suggestions</a></li> + <li><a href="#TheBasics">The Basics</a></li> + <li><a href="#SearchableConfiguration">Creating a Searchable Configuration</a></li> + <li><a href="#SearchableActivity">Creating a Searchable Activity</a> + <ol> + <li><a href="#DeclaringSearchableActivity">Declaring a searchable activity</a></li> + <li><a href="#PerformingSearch">Performing a search</a></li> + </ol> + </li> + <li><a href="#SearchDialog">Using the Search Dialog</a> + <ol> + <li><a href="#InvokingTheSearchDialog">Invoking the search dialog</a></li> + <li><a href="#LifeCycle">The impact of the search dialog on your activity lifecycle</a></li> + <li><a href="#SearchContextData">Passing search context data</a></li> + </ol> + </li> + <li><a href="#UsingSearchWidget">Using the Search Widget</a> + <ol> + <li><a href="#ConfiguringWidget">Configuring the search widget</a></li> + <li><a href="#WidgetFeatures">Other search widget features</a></li> + <li><a href="#UsingBoth">Using both the widget and the dialog</a></li> + </ol> + </li> + <li><a href="#VoiceSearch">Adding Voice Search</a></li> + <li><a href="#SearchSuggestions">Adding Search Suggestions</a></li> </ol> <h2>Key classes</h2> @@ -494,13 +495,13 @@ searches.</p> <h3 id="InvokingTheSearchDialog">Invoking the search dialog</h3> -<p>As mentioned above, the device SEARCH button and {@link android.app.Activity#onSearchRequested -onSearchRequested()} method will open the search dialog, as long as the current activity -has declared the searchable activity to use, as shown in the previous section.</p> +<p>As mentioned above, the device SEARCH button will open the search dialog as long as the current +activity has declared in the manifest the searchable activity to use.</p> -<p>However, you should not assume that a SEARCH button is available on the user's device. You -should always provide another search button in your UI that activates the search dialog by calling -{@link android.app.Activity#onSearchRequested()}.</p> +<p>However, some devices do not include a dedicated SEARCH button, so you should not assume that +it's always available. When using the search dialog, you must <strong>always provide another search +button in your UI</strong> that activates the search dialog by calling {@link +android.app.Activity#onSearchRequested()}.</p> <p>For instance, you should either provide a menu item in your <a href="{@docRoot}guide/topics/ui/menus.html#options-menu">Options Menu</a> or a button in your @@ -510,12 +511,6 @@ href="{@docRoot}shareables/search_icons.zip">search_icons.zip</a> file includes medium and high density screens, which you can use for your search menu item or button (low-density screens scale-down the hdpi image by one half). </p> -<!-- ... maybe this should go into the Creating Menus document .... -<p>If you chose to provide a shortcut key for the menu item, using {@link -android.view.MenuItem#setAlphabeticShortcut(char)}, then SearchManager.MENU_KEY is the recommended -key character, representing the default search key.</p> ---> - <p>You can also enable "type-to-search" functionality, which activates the search dialog when the user starts typing on the keyboard—the keystrokes are inserted into the search dialog. You can enable type-to-search in your activity by calling diff --git a/docs/html/resources/tutorials/views/hello-spinner.jd b/docs/html/resources/tutorials/views/hello-spinner.jd index 7a3a9c3c1e82..e9dc20fb7ba8 100644 --- a/docs/html/resources/tutorials/views/hello-spinner.jd +++ b/docs/html/resources/tutorials/views/hello-spinner.jd @@ -105,7 +105,7 @@ public class MyOnItemSelectedListener implements OnItemSelectedListener { public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) { - Toast.makeText(parent.getContext()), "The planet is " + + Toast.makeText(parent.getContext(), "The planet is " + parent.getItemAtPosition(pos).toString(), Toast.LENGTH_LONG).show(); } diff --git a/docs/html/sdk/oem-usb.jd b/docs/html/sdk/oem-usb.jd index 27e742a0bec1..3c2ba8b6501d 100644 --- a/docs/html/sdk/oem-usb.jd +++ b/docs/html/sdk/oem-usb.jd @@ -80,6 +80,12 @@ href="http://www.kttech.co.kr/cscenter/download05.asp">http://www.kttech.co.kr/c <td><a href="http://www.kyocera-wireless.com/support/phone_drivers.htm">http://www.kyocera-wireless.com/support/phone_drivers.htm</a> </td> </tr> + <tr> + <td>Lenevo</td> + <td><a href="http://developer.lenovomm.com/developer/download.jsp" + >http://developer.lenovomm.com/developer/download.jsp</a> + </td> + </tr> <tr><td>LGE</td> <td><a href="http://www.lg.com/us/mobile-phones/mobile-support/mobile-lg-mobile-phone-support.jsp">http://www.lg.com/us/mobile-phones/mobile-support/mobile-lg-mobile-phone-support.jsp</a></td> </tr><tr><td>Motorola</td> <td><a diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index 12dc93c3c07d..40d54bb0d580 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -681,7 +681,8 @@ public final class Bitmap implements Parcelable { */ public enum CompressFormat { JPEG (0), - PNG (1); + PNG (1), + WEBP (2); CompressFormat(int nativeInt) { this.nativeInt = nativeInt; diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java index a9414e800e69..b0f7fd3e1e80 100644 --- a/graphics/java/android/graphics/drawable/DrawableContainer.java +++ b/graphics/java/android/graphics/drawable/DrawableContainer.java @@ -167,6 +167,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { } if (mCurrDrawable != null) { mCurrDrawable.jumpToCurrentState(); + mCurrDrawable.setAlpha(mAlpha); } if (mExitAnimationEnd != 0) { mExitAnimationEnd = 0; diff --git a/include/binder/IMemory.h b/include/binder/IMemory.h index 74d2cc781689..2d0db001c654 100644 --- a/include/binder/IMemory.h +++ b/include/binder/IMemory.h @@ -43,6 +43,7 @@ public: virtual void* getBase() const = 0; virtual size_t getSize() const = 0; virtual uint32_t getFlags() const = 0; + virtual uint32_t getOffset() const = 0; // these are there just for backward source compatibility int32_t heapID() const { return getHeapID(); } diff --git a/include/binder/MemoryHeapBase.h b/include/binder/MemoryHeapBase.h index 2f2e31b38006..bbbda9c16464 100644 --- a/include/binder/MemoryHeapBase.h +++ b/include/binder/MemoryHeapBase.h @@ -27,7 +27,7 @@ namespace android { // --------------------------------------------------------------------------- -class MemoryHeapBase : public virtual BnMemoryHeap +class MemoryHeapBase : public virtual BnMemoryHeap { public: enum { @@ -38,12 +38,12 @@ public: NO_CACHING = 0x00000200 }; - /* + /* * maps the memory referenced by fd. but DOESN'T take ownership * of the filedescriptor (it makes a copy with dup() */ MemoryHeapBase(int fd, size_t size, uint32_t flags = 0, uint32_t offset = 0); - + /* * maps memory from the given device */ @@ -61,9 +61,10 @@ public: virtual void* getBase() const; virtual size_t getSize() const; virtual uint32_t getFlags() const; + virtual uint32_t getOffset() const; const char* getDevice() const; - + /* this closes this heap -- use carefully */ void dispose(); @@ -74,12 +75,12 @@ public: mDevice = device; return mDevice ? NO_ERROR : ALREADY_EXISTS; } - + protected: MemoryHeapBase(); // init() takes ownership of fd status_t init(int fd, void *base, int size, - int flags = 0, const char* device = NULL); + int flags = 0, const char* device = NULL); private: status_t mapfd(int fd, size_t size, uint32_t offset = 0); @@ -90,6 +91,7 @@ private: uint32_t mFlags; const char* mDevice; bool mNeedUnmap; + uint32_t mOffset; }; // --------------------------------------------------------------------------- diff --git a/include/camera/Camera.h b/include/camera/Camera.h index 7106bfa6606f..f70128063d70 100644 --- a/include/camera/Camera.h +++ b/include/camera/Camera.h @@ -18,9 +18,11 @@ #define ANDROID_HARDWARE_CAMERA_H #include <utils/Timers.h> -#include <camera/ICameraClient.h> #include <gui/ISurfaceTexture.h> #include <system/camera.h> +#include <camera/ICameraClient.h> +#include <camera/ICameraRecordingProxy.h> +#include <camera/ICameraRecordingProxyListener.h> namespace android { @@ -70,7 +72,7 @@ public: static status_t getCameraInfo(int cameraId, struct CameraInfo* cameraInfo); static sp<Camera> connect(int cameraId); - ~Camera(); + virtual ~Camera(); void init(); status_t reconnect(); @@ -129,8 +131,11 @@ public: status_t storeMetaDataInBuffers(bool enabled); void setListener(const sp<CameraListener>& listener); + void setRecordingProxyListener(const sp<ICameraRecordingProxyListener>& listener); void setPreviewCallbackFlags(int preview_callback_flag); + sp<ICameraRecordingProxy> getRecordingProxy(); + // ICameraClient interface virtual void notifyCallback(int32_t msgType, int32_t ext, int32_t ext2); virtual void dataCallback(int32_t msgType, const sp<IMemory>& dataPtr); @@ -138,6 +143,20 @@ public: sp<ICamera> remote(); + class RecordingProxy : public BnCameraRecordingProxy + { + public: + RecordingProxy(const sp<Camera>& camera); + + // ICameraRecordingProxy interface + virtual status_t startRecording(const sp<ICameraRecordingProxyListener>& listener); + virtual void stopRecording(); + virtual void releaseRecordingFrame(const sp<IMemory>& mem); + + private: + sp<Camera> mCamera; + }; + private: Camera(); Camera(const Camera&); @@ -162,12 +181,12 @@ private: status_t mStatus; sp<CameraListener> mListener; + sp<ICameraRecordingProxyListener> mRecordingProxyListener; friend class DeathNotifier; static Mutex mLock; static sp<ICameraService> mCameraService; - }; }; // namespace android diff --git a/include/camera/ICameraRecordingProxy.h b/include/camera/ICameraRecordingProxy.h new file mode 100644 index 000000000000..2aac28465c3b --- /dev/null +++ b/include/camera/ICameraRecordingProxy.h @@ -0,0 +1,101 @@ +/* + * 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 ANDROID_HARDWARE_ICAMERA_RECORDING_PROXY_H +#define ANDROID_HARDWARE_ICAMERA_RECORDING_PROXY_H + +#include <binder/IInterface.h> +#include <utils/RefBase.h> + +namespace android { + +class ICameraRecordingProxyListener; +class IMemory; +class Parcel; + +/* + * The purpose of ICameraRecordingProxy and ICameraRecordingProxyListener is to + * allow applications using the camera during recording. + * + * Camera service allows only one client at a time. Since camcorder application + * needs to own the camera to do things like zoom, the media recorder cannot + * access the camera directly during recording. So ICameraRecordingProxy is a + * proxy of ICamera, which allows the media recorder to start/stop the recording + * and release recording frames. ICameraRecordingProxyListener is an interface + * that allows the recorder to receive video frames during recording. + * + * ICameraRecordingProxy + * startRecording() + * stopRecording() + * releaseRecordingFrame() + * + * ICameraRecordingProxyListener + * dataCallbackTimestamp() + + * The camcorder app opens the camera and starts the preview. The app passes + * ICamera and ICameraRecordingProxy to the media recorder by + * MediaRecorder::setCamera(). The recorder uses ICamera to setup the camera in + * MediaRecorder::start(). After setup, the recorder disconnects from camera + * service. The recorder calls ICameraRecordingProxy::startRecording() and + * passes a ICameraRecordingProxyListener to the app. The app connects back to + * camera service and starts the recording. The app owns the camera and can do + * things like zoom. The media recorder receives the video frames from the + * listener and releases them by ICameraRecordingProxy::releaseRecordingFrame. + * The recorder calls ICameraRecordingProxy::stopRecording() to stop the + * recording. + * + * The call sequences are as follows: + * 1. The app: Camera.unlock(). + * 2. The app: MediaRecorder.setCamera(). + * 3. Start recording + * (1) The app: MediaRecorder.start(). + * (2) The recorder: ICamera.unlock() and ICamera.disconnect(). + * (3) The recorder: ICameraRecordingProxy.startRecording(). + * (4) The app: ICamera.reconnect(). + * (5) The app: ICamera.startRecording(). + * 4. During recording + * (1) The recorder: receive frames from ICameraRecordingProxyListener.dataCallbackTimestamp() + * (2) The recorder: release frames by ICameraRecordingProxy.releaseRecordingFrame(). + * 5. Stop recording + * (1) The app: MediaRecorder.stop() + * (2) The recorder: ICameraRecordingProxy.stopRecording(). + * (3) The app: ICamera.stopRecording(). + */ + +class ICameraRecordingProxy: public IInterface +{ +public: + DECLARE_META_INTERFACE(CameraRecordingProxy); + + virtual status_t startRecording(const sp<ICameraRecordingProxyListener>& listener) = 0; + virtual void stopRecording() = 0; + virtual void releaseRecordingFrame(const sp<IMemory>& mem) = 0; +}; + +// ---------------------------------------------------------------------------- + +class BnCameraRecordingProxy: public BnInterface<ICameraRecordingProxy> +{ +public: + virtual status_t onTransact( uint32_t code, + const Parcel& data, + Parcel* reply, + uint32_t flags = 0); +}; + +}; // namespace android + +#endif diff --git a/include/camera/ICameraRecordingProxyListener.h b/include/camera/ICameraRecordingProxyListener.h new file mode 100644 index 000000000000..b6c062442fd6 --- /dev/null +++ b/include/camera/ICameraRecordingProxyListener.h @@ -0,0 +1,52 @@ +/* + * 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 ANDROID_HARDWARE_ICAMERA_RECORDING_PROXY_LISTENER_H +#define ANDROID_HARDWARE_ICAMERA_RECORDING_PROXY_LISTENER_H + +#include <binder/IInterface.h> +#include <stdint.h> +#include <utils/RefBase.h> +#include <utils/Timers.h> + +namespace android { + +class Parcel; +class IMemory; + +class ICameraRecordingProxyListener: public IInterface +{ +public: + DECLARE_META_INTERFACE(CameraRecordingProxyListener); + + virtual void dataCallbackTimestamp(nsecs_t timestamp, int32_t msgType, + const sp<IMemory>& data) = 0; +}; + +// ---------------------------------------------------------------------------- + +class BnCameraRecordingProxyListener: public BnInterface<ICameraRecordingProxyListener> +{ +public: + virtual status_t onTransact( uint32_t code, + const Parcel& data, + Parcel* reply, + uint32_t flags = 0); +}; + +}; // namespace android + +#endif diff --git a/include/media/IMediaRecorder.h b/include/media/IMediaRecorder.h index 28be7c100c36..a73267d92587 100644 --- a/include/media/IMediaRecorder.h +++ b/include/media/IMediaRecorder.h @@ -24,6 +24,7 @@ namespace android { class Surface; class ICamera; +class ICameraRecordingProxy; class IMediaRecorderClient; class IMediaRecorder: public IInterface @@ -31,28 +32,29 @@ class IMediaRecorder: public IInterface public: DECLARE_META_INTERFACE(MediaRecorder); - virtual status_t setCamera(const sp<ICamera>& camera) = 0; - virtual status_t setPreviewSurface(const sp<Surface>& surface) = 0; - virtual status_t setVideoSource(int vs) = 0; - virtual status_t setAudioSource(int as) = 0; - virtual status_t setOutputFormat(int of) = 0; - virtual status_t setVideoEncoder(int ve) = 0; - virtual status_t setAudioEncoder(int ae) = 0; - virtual status_t setOutputFile(const char* path) = 0; - virtual status_t setOutputFile(int fd, int64_t offset, int64_t length) = 0; - virtual status_t setOutputFileAuxiliary(int fd) = 0; - virtual status_t setVideoSize(int width, int height) = 0; - virtual status_t setVideoFrameRate(int frames_per_second) = 0; - virtual status_t setParameters(const String8& params) = 0; - virtual status_t setListener(const sp<IMediaRecorderClient>& listener) = 0; - virtual status_t prepare() = 0; - virtual status_t getMaxAmplitude(int* max) = 0; - virtual status_t start() = 0; - virtual status_t stop() = 0; - virtual status_t reset() = 0; - virtual status_t init() = 0; - virtual status_t close() = 0; - virtual status_t release() = 0; + virtual status_t setCamera(const sp<ICamera>& camera, + const sp<ICameraRecordingProxy>& proxy) = 0; + virtual status_t setPreviewSurface(const sp<Surface>& surface) = 0; + virtual status_t setVideoSource(int vs) = 0; + virtual status_t setAudioSource(int as) = 0; + virtual status_t setOutputFormat(int of) = 0; + virtual status_t setVideoEncoder(int ve) = 0; + virtual status_t setAudioEncoder(int ae) = 0; + virtual status_t setOutputFile(const char* path) = 0; + virtual status_t setOutputFile(int fd, int64_t offset, int64_t length) = 0; + virtual status_t setOutputFileAuxiliary(int fd) = 0; + virtual status_t setVideoSize(int width, int height) = 0; + virtual status_t setVideoFrameRate(int frames_per_second) = 0; + virtual status_t setParameters(const String8& params) = 0; + virtual status_t setListener(const sp<IMediaRecorderClient>& listener) = 0; + virtual status_t prepare() = 0; + virtual status_t getMaxAmplitude(int* max) = 0; + virtual status_t start() = 0; + virtual status_t stop() = 0; + virtual status_t reset() = 0; + virtual status_t init() = 0; + virtual status_t close() = 0; + virtual status_t release() = 0; }; // ---------------------------------------------------------------------------- diff --git a/include/media/MediaRecorderBase.h b/include/media/MediaRecorderBase.h index 7e22a24c3483..1c08969a0d5c 100644 --- a/include/media/MediaRecorderBase.h +++ b/include/media/MediaRecorderBase.h @@ -24,6 +24,7 @@ namespace android { +class ICameraRecordingProxy; class Surface; struct MediaRecorderBase { @@ -38,7 +39,8 @@ struct MediaRecorderBase { virtual status_t setVideoEncoder(video_encoder ve) = 0; virtual status_t setVideoSize(int width, int height) = 0; virtual status_t setVideoFrameRate(int frames_per_second) = 0; - virtual status_t setCamera(const sp<ICamera>& camera) = 0; + virtual status_t setCamera(const sp<ICamera>& camera, + const sp<ICameraRecordingProxy>& proxy) = 0; virtual status_t setPreviewSurface(const sp<Surface>& surface) = 0; virtual status_t setOutputFile(const char *path) = 0; virtual status_t setOutputFile(int fd, int64_t offset, int64_t length) = 0; diff --git a/include/media/mediarecorder.h b/include/media/mediarecorder.h index 36bf34ebbd10..af12d3c59c1f 100644 --- a/include/media/mediarecorder.h +++ b/include/media/mediarecorder.h @@ -30,6 +30,7 @@ namespace android { class Surface; class IMediaRecorder; class ICamera; +class ICameraRecordingProxy; typedef void (*media_completion_f)(status_t status, void *cookie); @@ -202,7 +203,7 @@ public: void died(); status_t initCheck(); - status_t setCamera(const sp<ICamera>& camera); + status_t setCamera(const sp<ICamera>& camera, const sp<ICameraRecordingProxy>& proxy); status_t setPreviewSurface(const sp<Surface>& surface); status_t setVideoSource(int vs); status_t setAudioSource(int as); diff --git a/include/media/stagefright/CameraSource.h b/include/media/stagefright/CameraSource.h index bb25bae3007e..80b7c1cfb15e 100644 --- a/include/media/stagefright/CameraSource.h +++ b/include/media/stagefright/CameraSource.h @@ -21,6 +21,7 @@ #include <media/stagefright/MediaBuffer.h> #include <media/stagefright/MediaSource.h> #include <camera/ICamera.h> +#include <camera/ICameraRecordingProxyListener.h> #include <camera/CameraParameters.h> #include <utils/List.h> #include <utils/RefBase.h> @@ -68,6 +69,7 @@ public: * @return NULL on error. */ static CameraSource *CreateFromCamera(const sp<ICamera> &camera, + const sp<ICameraRecordingProxy> &proxy, int32_t cameraId, Size videoSize, int32_t frameRate, @@ -111,6 +113,23 @@ public: virtual void signalBufferReturned(MediaBuffer* buffer); protected: + class ProxyListener: public BnCameraRecordingProxyListener { + public: + ProxyListener(const sp<CameraSource>& source); + virtual void dataCallbackTimestamp(int64_t timestampUs, int32_t msgType, + const sp<IMemory> &data); + + private: + sp<CameraSource> mSource; + }; + + // isBinderAlive needs linkToDeath to work. + class DeathNotifier: public IBinder::DeathRecipient { + public: + DeathNotifier() {} + virtual void binderDied(const wp<IBinder>& who); + }; + enum CameraFlags { FLAGS_SET_CAMERA = 1L << 0, FLAGS_HOT_CAMERA = 1L << 1, @@ -123,6 +142,8 @@ protected: status_t mInitCheck; sp<Camera> mCamera; + sp<ICameraRecordingProxy> mCameraRecordingProxy; + sp<DeathNotifier> mDeathNotifier; sp<Surface> mSurface; sp<MetaData> mMeta; @@ -132,7 +153,8 @@ protected: bool mStarted; int32_t mNumFramesEncoded; - CameraSource(const sp<ICamera>& camera, int32_t cameraId, + CameraSource(const sp<ICamera>& camera, const sp<ICameraRecordingProxy>& proxy, + int32_t cameraId, Size videoSize, int32_t frameRate, const sp<Surface>& surface, bool storeMetaDataInVideoBuffers); @@ -172,10 +194,12 @@ private: void releaseOneRecordingFrame(const sp<IMemory>& frame); - status_t init(const sp<ICamera>& camera, int32_t cameraId, - Size videoSize, int32_t frameRate, - bool storeMetaDataInVideoBuffers); - status_t isCameraAvailable(const sp<ICamera>& camera, int32_t cameraId); + status_t init(const sp<ICamera>& camera, const sp<ICameraRecordingProxy>& proxy, + int32_t cameraId, Size videoSize, int32_t frameRate, + bool storeMetaDataInVideoBuffers); + status_t isCameraAvailable(const sp<ICamera>& camera, + const sp<ICameraRecordingProxy>& proxy, + int32_t cameraId); status_t isCameraColorFormatSupported(const CameraParameters& params); status_t configureCamera(CameraParameters* params, int32_t width, int32_t height, diff --git a/include/media/stagefright/CameraSourceTimeLapse.h b/include/media/stagefright/CameraSourceTimeLapse.h index 0e5d534b836c..f07ebbab2803 100644 --- a/include/media/stagefright/CameraSourceTimeLapse.h +++ b/include/media/stagefright/CameraSourceTimeLapse.h @@ -33,6 +33,7 @@ class CameraSourceTimeLapse : public CameraSource { public: static CameraSourceTimeLapse *CreateFromCamera( const sp<ICamera> &camera, + const sp<ICameraRecordingProxy> &proxy, int32_t cameraId, Size videoSize, int32_t videoFrameRate, @@ -132,6 +133,7 @@ private: CameraSourceTimeLapse( const sp<ICamera> &camera, + const sp<ICameraRecordingProxy> &proxy, int32_t cameraId, Size videoSize, int32_t videoFrameRate, diff --git a/keystore/java/android/security/Credentials.java b/keystore/java/android/security/Credentials.java index fd6c22c2b945..ab4b9e04d671 100644 --- a/keystore/java/android/security/Credentials.java +++ b/keystore/java/android/security/Credentials.java @@ -71,6 +71,13 @@ public class Credentials { /** Data type for PKCS12. */ public static final String PKCS12 = "PKCS12"; + // historically used by Android + public static final String EXTENSION_CRT = ".crt"; + public static final String EXTENSION_P12 = ".p12"; + // commonly used on Windows + public static final String EXTENSION_CER = ".cer"; + public static final String EXTENSION_PFX = ".pfx"; + /** * Convert objects to a PEM format, which is used for * CA_CERTIFICATE, USER_CERTIFICATE, and USER_PRIVATE_KEY @@ -130,6 +137,15 @@ public class Credentials { return intent; } + public void install(Context context) { + try { + Intent intent = createInstallIntent(); + context.startActivity(intent); + } catch (ActivityNotFoundException e) { + Log.w(LOGTAG, e.toString()); + } + } + public void install(Context context, KeyPair pair) { try { Intent intent = createInstallIntent(); diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl index 2763e46f3bef..23ffd59f2fa6 100644 --- a/keystore/java/android/security/IKeyChainService.aidl +++ b/keystore/java/android/security/IKeyChainService.aidl @@ -30,5 +30,6 @@ interface IKeyChainService { void installCaCertificate(in byte[] caCertificate); // APIs used by Settings + boolean deleteCaCertificate(String alias); boolean reset(); } diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java index 4f1596df8b02..18011e618f49 100644 --- a/keystore/java/android/security/KeyChain.java +++ b/keystore/java/android/security/KeyChain.java @@ -22,6 +22,7 @@ import android.accounts.AccountManagerFuture; import android.accounts.AuthenticatorException; import android.accounts.OperationCanceledException; import android.app.Activity; +import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -93,6 +94,26 @@ public final class KeyChain { public static final String EXTRA_RESPONSE = "response"; /** + * @hide Also used by KeyChainActivity implementation + */ + public static final String EXTRA_HOST = "host"; + + /** + * @hide Also used by KeyChainActivity implementation + */ + public static final String EXTRA_PORT = "port"; + + /** + * @hide Also used by KeyChainActivity implementation + */ + public static final String EXTRA_ALIAS = "alias"; + + /** + * @hide Also used by KeyChainActivity implementation + */ + public static final String EXTRA_SENDER = "sender"; + + /** * Launches an {@code Activity} for the user to select the alias * for a private key and certificate pair for authentication. The * selected alias or null will be returned via the @@ -106,6 +127,9 @@ public final class KeyChain { * <p>{@code host} and {@code port} may be used to give the user * more context about the server requesting the credentials. * + * <p>{@code alias} allows the chooser to preselect an existing + * alias which will still be subject to user confirmation. + * * <p>This method requires the caller to hold the permission * {@link android.Manifest.permission#USE_CREDENTIALS}. * @@ -123,14 +147,17 @@ public final class KeyChain { * certificate, or null if unavailable. * @param port The port number of the server requesting the * certificate, or -1 if unavailable. + * @param alias The alias to preselect if available, or null if + * unavailable. */ public static void choosePrivateKeyAlias(Activity activity, KeyChainAliasCallback response, String[] keyTypes, Principal[] issuers, - String host, int port) { + String host, int port, + String alias) { /* - * TODO currently keyTypes, issuers, host, and port are - * unused. They are meant to follow the semantics and purpose - * of X509KeyManager method arguments. + * TODO currently keyTypes, issuers are unused. They are meant + * to follow the semantics and purpose of X509KeyManager + * method arguments. * * keyTypes would allow the list to be filtered and typically * will be set correctly by the server. In practice today, @@ -142,11 +169,6 @@ public final class KeyChain { * server. Others will send none. If this is used, if there * are no matches after applying the constraint, it should be * ignored. - * - * host and port may be shown to the user if available, but it - * should be clear that they are not validated values, perhaps - * shown along with requesting application identity to clarify - * the source of the request. */ if (activity == null) { throw new NullPointerException("activity == null"); @@ -156,6 +178,11 @@ public final class KeyChain { } Intent intent = new Intent("com.android.keychain.CHOOSER"); intent.putExtra(EXTRA_RESPONSE, new AliasResponse(activity, response)); + intent.putExtra(EXTRA_HOST, host); + intent.putExtra(EXTRA_PORT, port); + intent.putExtra(EXTRA_ALIAS, alias); + // the PendingIntent is used to get calling package name + intent.putExtra(EXTRA_SENDER, PendingIntent.getActivity(activity, 0, new Intent(), 0)); activity.startActivity(intent); } diff --git a/keystore/tests/src/android/security/KeyStoreTest.java b/keystore/tests/src/android/security/KeyStoreTest.java index 4582aa0ccfe8..15e253cee33e 100755 --- a/keystore/tests/src/android/security/KeyStoreTest.java +++ b/keystore/tests/src/android/security/KeyStoreTest.java @@ -75,11 +75,21 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { assertEquals(KeyStore.State.UNLOCKED, mKeyStore.state()); } + public void testGet() throws Exception { + assertNull(mKeyStore.get(TEST_KEYNAME)); + mKeyStore.password(TEST_PASSWD); + assertNull(mKeyStore.get(TEST_KEYNAME)); + assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE)); + assertTrue(Arrays.equals(TEST_KEYVALUE, mKeyStore.get(TEST_KEYNAME))); + } + public void testPut() throws Exception { + assertNull(mKeyStore.get(TEST_KEYNAME)); assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE)); assertFalse(mKeyStore.contains(TEST_KEYNAME)); mKeyStore.password(TEST_PASSWD); assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE)); + assertTrue(Arrays.equals(TEST_KEYVALUE, mKeyStore.get(TEST_KEYNAME))); } public void testI18n() throws Exception { @@ -96,7 +106,9 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { assertTrue(mKeyStore.delete(TEST_KEYNAME)); mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE); + assertTrue(Arrays.equals(TEST_KEYVALUE, mKeyStore.get(TEST_KEYNAME))); assertTrue(mKeyStore.delete(TEST_KEYNAME)); + assertNull(mKeyStore.get(TEST_KEYNAME)); } public void testContains() throws Exception { diff --git a/libs/binder/IMemory.cpp b/libs/binder/IMemory.cpp index bc8c412fb065..1ace8f829194 100644 --- a/libs/binder/IMemory.cpp +++ b/libs/binder/IMemory.cpp @@ -42,11 +42,11 @@ class HeapCache : public IBinder::DeathRecipient public: HeapCache(); virtual ~HeapCache(); - + virtual void binderDied(const wp<IBinder>& who); - sp<IMemoryHeap> find_heap(const sp<IBinder>& binder); - void free_heap(const sp<IBinder>& binder); + sp<IMemoryHeap> find_heap(const sp<IBinder>& binder); + void free_heap(const sp<IBinder>& binder); sp<IMemoryHeap> get_heap(const sp<IBinder>& binder); void dump_heaps(); @@ -57,7 +57,7 @@ private: int32_t count; }; - void free_heap(const wp<IBinder>& binder); + void free_heap(const wp<IBinder>& binder); Mutex mHeapCacheLock; KeyedVector< wp<IBinder>, heap_info_t > mHeapCache; @@ -81,11 +81,12 @@ public: virtual void* getBase() const; virtual size_t getSize() const; virtual uint32_t getFlags() const; + virtual uint32_t getOffset() const; private: friend class IMemory; friend class HeapCache; - + // for debugging in this module static inline sp<IMemoryHeap> find_heap(const sp<IBinder>& binder) { return gHeapCache->find_heap(binder); @@ -97,7 +98,7 @@ private: return gHeapCache->get_heap(binder); } static inline void dump_heaps() { - gHeapCache->dump_heaps(); + gHeapCache->dump_heaps(); } void assertMapped() const; @@ -107,6 +108,7 @@ private: mutable void* mBase; mutable size_t mSize; mutable uint32_t mFlags; + mutable uint32_t mOffset; mutable bool mRealHeap; mutable Mutex mLock; }; @@ -123,7 +125,7 @@ public: BpMemory(const sp<IBinder>& impl); virtual ~BpMemory(); virtual sp<IMemoryHeap> getMemory(ssize_t* offset=0, size_t* size=0) const; - + private: mutable sp<IMemoryHeap> mHeap; mutable ssize_t mOffset; @@ -203,7 +205,7 @@ IMPLEMENT_META_INTERFACE(Memory, "android.utils.IMemory"); BnMemory::BnMemory() { } -BnMemory::~BnMemory() { +BnMemory::~BnMemory() { } status_t BnMemory::onTransact( @@ -229,7 +231,7 @@ status_t BnMemory::onTransact( BpMemoryHeap::BpMemoryHeap(const sp<IBinder>& impl) : BpInterface<IMemoryHeap>(impl), - mHeapId(-1), mBase(MAP_FAILED), mSize(0), mFlags(0), mRealHeap(false) + mHeapId(-1), mBase(MAP_FAILED), mSize(0), mFlags(0), mOffset(0), mRealHeap(false) { } @@ -242,7 +244,7 @@ BpMemoryHeap::~BpMemoryHeap() { sp<IBinder> binder = const_cast<BpMemoryHeap*>(this)->asBinder(); if (VERBOSE) { - LOGD("UNMAPPING binder=%p, heap=%p, size=%d, fd=%d", + LOGD("UNMAPPING binder=%p, heap=%p, size=%d, fd=%d", binder.get(), this, mSize, mHeapId); CallStack stack; stack.update(); @@ -270,6 +272,7 @@ void BpMemoryHeap::assertMapped() const if (mHeapId == -1) { mBase = heap->mBase; mSize = heap->mSize; + mOffset = heap->mOffset; android_atomic_write( dup( heap->mHeapId ), &mHeapId ); } } else { @@ -286,13 +289,14 @@ void BpMemoryHeap::assertReallyMapped() const // remote call without mLock held, worse case scenario, we end up // calling transact() from multiple threads, but that's not a problem, // only mmap below must be in the critical section. - + Parcel data, reply; data.writeInterfaceToken(IMemoryHeap::getInterfaceDescriptor()); status_t err = remote()->transact(HEAP_ID, data, &reply); int parcel_fd = reply.readFileDescriptor(); ssize_t size = reply.readInt32(); uint32_t flags = reply.readInt32(); + uint32_t offset = reply.readInt32(); LOGE_IF(err, "binder=%p transaction failed fd=%d, size=%ld, err=%d (%s)", asBinder().get(), parcel_fd, size, err, strerror(-err)); @@ -309,7 +313,7 @@ void BpMemoryHeap::assertReallyMapped() const Mutex::Autolock _l(mLock); if (mHeapId == -1) { mRealHeap = true; - mBase = mmap(0, size, access, MAP_SHARED, fd, 0); + mBase = mmap(0, size, access, MAP_SHARED, fd, offset); if (mBase == MAP_FAILED) { LOGE("cannot map BpMemoryHeap (binder=%p), size=%ld, fd=%d (%s)", asBinder().get(), size, fd, strerror(errno)); @@ -317,6 +321,7 @@ void BpMemoryHeap::assertReallyMapped() const } else { mSize = size; mFlags = flags; + mOffset = offset; android_atomic_write(fd, &mHeapId); } } @@ -343,14 +348,19 @@ uint32_t BpMemoryHeap::getFlags() const { return mFlags; } +uint32_t BpMemoryHeap::getOffset() const { + assertMapped(); + return mOffset; +} + // --------------------------------------------------------------------------- IMPLEMENT_META_INTERFACE(MemoryHeap, "android.utils.IMemoryHeap"); -BnMemoryHeap::BnMemoryHeap() { +BnMemoryHeap::BnMemoryHeap() { } -BnMemoryHeap::~BnMemoryHeap() { +BnMemoryHeap::~BnMemoryHeap() { } status_t BnMemoryHeap::onTransact( @@ -362,6 +372,7 @@ status_t BnMemoryHeap::onTransact( reply->writeFileDescriptor(getHeapID()); reply->writeInt32(getSize()); reply->writeInt32(getFlags()); + reply->writeInt32(getOffset()); return NO_ERROR; } break; default: @@ -383,17 +394,17 @@ HeapCache::~HeapCache() void HeapCache::binderDied(const wp<IBinder>& binder) { //LOGD("binderDied binder=%p", binder.unsafe_get()); - free_heap(binder); + free_heap(binder); } -sp<IMemoryHeap> HeapCache::find_heap(const sp<IBinder>& binder) +sp<IMemoryHeap> HeapCache::find_heap(const sp<IBinder>& binder) { Mutex::Autolock _l(mHeapCacheLock); ssize_t i = mHeapCache.indexOfKey(binder); if (i>=0) { heap_info_t& info = mHeapCache.editValueAt(i); LOGD_IF(VERBOSE, - "found binder=%p, heap=%p, size=%d, fd=%d, count=%d", + "found binder=%p, heap=%p, size=%d, fd=%d, count=%d", binder.get(), info.heap.get(), static_cast<BpMemoryHeap*>(info.heap.get())->mSize, static_cast<BpMemoryHeap*>(info.heap.get())->mHeapId, @@ -415,7 +426,7 @@ void HeapCache::free_heap(const sp<IBinder>& binder) { free_heap( wp<IBinder>(binder) ); } -void HeapCache::free_heap(const wp<IBinder>& binder) +void HeapCache::free_heap(const wp<IBinder>& binder) { sp<IMemoryHeap> rel; { @@ -426,7 +437,7 @@ void HeapCache::free_heap(const wp<IBinder>& binder) int32_t c = android_atomic_dec(&info.count); if (c == 1) { LOGD_IF(VERBOSE, - "removing binder=%p, heap=%p, size=%d, fd=%d, count=%d", + "removing binder=%p, heap=%p, size=%d, fd=%d, count=%d", binder.unsafe_get(), info.heap.get(), static_cast<BpMemoryHeap*>(info.heap.get())->mSize, static_cast<BpMemoryHeap*>(info.heap.get())->mHeapId, @@ -450,7 +461,7 @@ sp<IMemoryHeap> HeapCache::get_heap(const sp<IBinder>& binder) return realHeap; } -void HeapCache::dump_heaps() +void HeapCache::dump_heaps() { Mutex::Autolock _l(mHeapCacheLock); int c = mHeapCache.size(); @@ -459,7 +470,7 @@ void HeapCache::dump_heaps() BpMemoryHeap const* h(static_cast<BpMemoryHeap const *>(info.heap.get())); LOGD("hey=%p, heap=%p, count=%d, (fd=%d, base=%p, size=%d)", mHeapCache.keyAt(i).unsafe_get(), - info.heap.get(), info.count, + info.heap.get(), info.count, h->mHeapId, h->mBase, h->mSize); } } diff --git a/libs/binder/MemoryHeapBase.cpp b/libs/binder/MemoryHeapBase.cpp index 9f501e277cea..bf4a73f38970 100644 --- a/libs/binder/MemoryHeapBase.cpp +++ b/libs/binder/MemoryHeapBase.cpp @@ -40,15 +40,15 @@ namespace android { // --------------------------------------------------------------------------- -MemoryHeapBase::MemoryHeapBase() +MemoryHeapBase::MemoryHeapBase() : mFD(-1), mSize(0), mBase(MAP_FAILED), - mDevice(NULL), mNeedUnmap(false) + mDevice(NULL), mNeedUnmap(false), mOffset(0) { } MemoryHeapBase::MemoryHeapBase(size_t size, uint32_t flags, char const * name) : mFD(-1), mSize(0), mBase(MAP_FAILED), mFlags(flags), - mDevice(0), mNeedUnmap(false) + mDevice(0), mNeedUnmap(false), mOffset(0) { const size_t pagesize = getpagesize(); size = ((size + pagesize-1) & ~(pagesize-1)); @@ -65,7 +65,7 @@ MemoryHeapBase::MemoryHeapBase(size_t size, uint32_t flags, char const * name) MemoryHeapBase::MemoryHeapBase(const char* device, size_t size, uint32_t flags) : mFD(-1), mSize(0), mBase(MAP_FAILED), mFlags(flags), - mDevice(0), mNeedUnmap(false) + mDevice(0), mNeedUnmap(false), mOffset(0) { int open_flags = O_RDWR; if (flags & NO_CACHING) @@ -84,7 +84,7 @@ MemoryHeapBase::MemoryHeapBase(const char* device, size_t size, uint32_t flags) MemoryHeapBase::MemoryHeapBase(int fd, size_t size, uint32_t flags, uint32_t offset) : mFD(-1), mSize(0), mBase(MAP_FAILED), mFlags(flags), - mDevice(0), mNeedUnmap(false) + mDevice(0), mNeedUnmap(false), mOffset(0) { const size_t pagesize = getpagesize(); size = ((size + pagesize-1) & ~(pagesize-1)); @@ -141,6 +141,7 @@ status_t MemoryHeapBase::mapfd(int fd, size_t size, uint32_t offset) } mFD = fd; mSize = size; + mOffset = offset; return NO_ERROR; } @@ -183,5 +184,9 @@ const char* MemoryHeapBase::getDevice() const { return mDevice; } +uint32_t MemoryHeapBase::getOffset() const { + return mOffset; +} + // --------------------------------------------------------------------------- }; // namespace android diff --git a/libs/camera/Android.mk b/libs/camera/Android.mk index b17b3d22044d..dc00957881c3 100644 --- a/libs/camera/Android.mk +++ b/libs/camera/Android.mk @@ -6,7 +6,9 @@ LOCAL_SRC_FILES:= \ CameraParameters.cpp \ ICamera.cpp \ ICameraClient.cpp \ - ICameraService.cpp + ICameraService.cpp \ + ICameraRecordingProxy.cpp \ + ICameraRecordingProxyListener.cpp LOCAL_SHARED_LIBRARIES := \ libcutils \ diff --git a/libs/camera/Camera.cpp b/libs/camera/Camera.cpp index 5eb48da3589e..3c00db556022 100644 --- a/libs/camera/Camera.cpp +++ b/libs/camera/Camera.cpp @@ -19,11 +19,12 @@ #define LOG_TAG "Camera" #include <utils/Log.h> #include <utils/threads.h> - +#include <binder/IPCThreadState.h> #include <binder/IServiceManager.h> #include <binder/IMemory.h> #include <camera/Camera.h> +#include <camera/ICameraRecordingProxyListener.h> #include <camera/ICameraService.h> #include <surfaceflinger/Surface.h> @@ -236,6 +237,10 @@ void Camera::stopPreview() void Camera::stopRecording() { LOGV("stopRecording"); + { + Mutex::Autolock _l(mLock); + mRecordingProxyListener.clear(); + } sp <ICamera> c = mCamera; if (c == 0) return; c->stopRecording(); @@ -327,6 +332,12 @@ void Camera::setListener(const sp<CameraListener>& listener) mListener = listener; } +void Camera::setRecordingProxyListener(const sp<ICameraRecordingProxyListener>& listener) +{ + Mutex::Autolock _l(mLock); + mRecordingProxyListener = listener; +} + void Camera::setPreviewCallbackFlags(int flag) { LOGV("setPreviewCallbackFlags"); @@ -364,6 +375,19 @@ void Camera::dataCallback(int32_t msgType, const sp<IMemory>& dataPtr) // callback from camera service when timestamped frame is ready void Camera::dataCallbackTimestamp(nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr) { + // If recording proxy listener is registered, forward the frame and return. + // The other listener (mListener) is ignored because the receiver needs to + // call releaseRecordingFrame. + sp<ICameraRecordingProxyListener> proxylistener; + { + Mutex::Autolock _l(mLock); + proxylistener = mRecordingProxyListener; + } + if (proxylistener != NULL) { + proxylistener->dataCallbackTimestamp(timestamp, msgType, dataPtr); + return; + } + sp<CameraListener> listener; { Mutex::Autolock _l(mLock); @@ -389,4 +413,34 @@ void Camera::DeathNotifier::binderDied(const wp<IBinder>& who) { LOGW("Camera server died!"); } +sp<ICameraRecordingProxy> Camera::getRecordingProxy() { + LOGV("getProxy"); + return new RecordingProxy(this); +} + +status_t Camera::RecordingProxy::startRecording(const sp<ICameraRecordingProxyListener>& listener) +{ + LOGV("RecordingProxy::startRecording"); + mCamera->setRecordingProxyListener(listener); + mCamera->reconnect(); + return mCamera->startRecording(); +} + +void Camera::RecordingProxy::stopRecording() +{ + LOGV("RecordingProxy::stopRecording"); + mCamera->stopRecording(); +} + +void Camera::RecordingProxy::releaseRecordingFrame(const sp<IMemory>& mem) +{ + LOGV("RecordingProxy::releaseRecordingFrame"); + mCamera->releaseRecordingFrame(mem); +} + +Camera::RecordingProxy::RecordingProxy(const sp<Camera>& camera) +{ + mCamera = camera; +} + }; // namespace android diff --git a/libs/camera/ICameraRecordingProxy.cpp b/libs/camera/ICameraRecordingProxy.cpp new file mode 100644 index 000000000000..64b6a5ceaacc --- /dev/null +++ b/libs/camera/ICameraRecordingProxy.cpp @@ -0,0 +1,109 @@ +/* + * 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "ICameraRecordingProxy" +#include <camera/ICameraRecordingProxy.h> +#include <camera/ICameraRecordingProxyListener.h> +#include <binder/IMemory.h> +#include <binder/Parcel.h> +#include <stdint.h> +#include <utils/Log.h> + +namespace android { + +enum { + START_RECORDING = IBinder::FIRST_CALL_TRANSACTION, + STOP_RECORDING, + RELEASE_RECORDING_FRAME, +}; + + +class BpCameraRecordingProxy: public BpInterface<ICameraRecordingProxy> +{ +public: + BpCameraRecordingProxy(const sp<IBinder>& impl) + : BpInterface<ICameraRecordingProxy>(impl) + { + } + + status_t startRecording(const sp<ICameraRecordingProxyListener>& listener) + { + LOGV("startRecording"); + Parcel data, reply; + data.writeInterfaceToken(ICameraRecordingProxy::getInterfaceDescriptor()); + data.writeStrongBinder(listener->asBinder()); + remote()->transact(START_RECORDING, data, &reply); + return reply.readInt32(); + } + + void stopRecording() + { + LOGV("stopRecording"); + Parcel data, reply; + data.writeInterfaceToken(ICameraRecordingProxy::getInterfaceDescriptor()); + remote()->transact(STOP_RECORDING, data, &reply); + } + + void releaseRecordingFrame(const sp<IMemory>& mem) + { + LOGV("releaseRecordingFrame"); + Parcel data, reply; + data.writeInterfaceToken(ICameraRecordingProxy::getInterfaceDescriptor()); + data.writeStrongBinder(mem->asBinder()); + remote()->transact(RELEASE_RECORDING_FRAME, data, &reply); + } +}; + +IMPLEMENT_META_INTERFACE(CameraRecordingProxy, "android.hardware.ICameraRecordingProxy"); + +// ---------------------------------------------------------------------- + +status_t BnCameraRecordingProxy::onTransact( + uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) +{ + switch(code) { + case START_RECORDING: { + LOGV("START_RECORDING"); + CHECK_INTERFACE(ICameraRecordingProxy, data, reply); + sp<ICameraRecordingProxyListener> listener = + interface_cast<ICameraRecordingProxyListener>(data.readStrongBinder()); + reply->writeInt32(startRecording(listener)); + return NO_ERROR; + } break; + case STOP_RECORDING: { + LOGV("STOP_RECORDING"); + CHECK_INTERFACE(ICameraRecordingProxy, data, reply); + stopRecording(); + return NO_ERROR; + } break; + case RELEASE_RECORDING_FRAME: { + LOGV("RELEASE_RECORDING_FRAME"); + CHECK_INTERFACE(ICameraRecordingProxy, data, reply); + sp<IMemory> mem = interface_cast<IMemory>(data.readStrongBinder()); + releaseRecordingFrame(mem); + return NO_ERROR; + } break; + + default: + return BBinder::onTransact(code, data, reply, flags); + } +} + +// ---------------------------------------------------------------------------- + +}; // namespace android + diff --git a/libs/camera/ICameraRecordingProxyListener.cpp b/libs/camera/ICameraRecordingProxyListener.cpp new file mode 100644 index 000000000000..f8cece5e1add --- /dev/null +++ b/libs/camera/ICameraRecordingProxyListener.cpp @@ -0,0 +1,75 @@ +/* + * 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "ICameraRecordingProxyListener" +#include <camera/ICameraRecordingProxyListener.h> +#include <binder/IMemory.h> +#include <binder/Parcel.h> +#include <utils/Log.h> + +namespace android { + +enum { + DATA_CALLBACK_TIMESTAMP = IBinder::FIRST_CALL_TRANSACTION, +}; + +class BpCameraRecordingProxyListener: public BpInterface<ICameraRecordingProxyListener> +{ +public: + BpCameraRecordingProxyListener(const sp<IBinder>& impl) + : BpInterface<ICameraRecordingProxyListener>(impl) + { + } + + void dataCallbackTimestamp(nsecs_t timestamp, int32_t msgType, const sp<IMemory>& imageData) + { + LOGV("dataCallback"); + Parcel data, reply; + data.writeInterfaceToken(ICameraRecordingProxyListener::getInterfaceDescriptor()); + data.writeInt64(timestamp); + data.writeInt32(msgType); + data.writeStrongBinder(imageData->asBinder()); + remote()->transact(DATA_CALLBACK_TIMESTAMP, data, &reply, IBinder::FLAG_ONEWAY); + } +}; + +IMPLEMENT_META_INTERFACE(CameraRecordingProxyListener, "android.hardware.ICameraRecordingProxyListener"); + +// ---------------------------------------------------------------------- + +status_t BnCameraRecordingProxyListener::onTransact( + uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) +{ + switch(code) { + case DATA_CALLBACK_TIMESTAMP: { + LOGV("DATA_CALLBACK_TIMESTAMP"); + CHECK_INTERFACE(ICameraRecordingProxyListener, data, reply); + nsecs_t timestamp = data.readInt64(); + int32_t msgType = data.readInt32(); + sp<IMemory> imageData = interface_cast<IMemory>(data.readStrongBinder()); + dataCallbackTimestamp(timestamp, msgType, imageData); + return NO_ERROR; + } break; + default: + return BBinder::onTransact(code, data, reply, flags); + } +} + +// ---------------------------------------------------------------------------- + +}; // namespace android + diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp index dfb5d3b510b2..e034a868a2b8 100644 --- a/libs/hwui/LayerRenderer.cpp +++ b/libs/hwui/LayerRenderer.cpp @@ -166,30 +166,6 @@ void LayerRenderer::generateMesh() { // Layers management /////////////////////////////////////////////////////////////////////////////// -Layer* LayerRenderer::createTextureLayer(bool isOpaque) { - LAYER_RENDERER_LOGD("Creating new texture layer"); - - Layer* layer = new Layer(0, 0); - layer->isCacheable = false; - layer->isTextureLayer = true; - layer->blend = !isOpaque; - layer->empty = true; - layer->fbo = 0; - layer->colorFilter = NULL; - layer->fbo = 0; - layer->layer.set(0.0f, 0.0f, 0.0f, 0.0f); - layer->texCoords.set(0.0f, 1.0f, 0.0f, 1.0f); - layer->alpha = 255; - layer->mode = SkXfermode::kSrcOver_Mode; - layer->colorFilter = NULL; - layer->region.clear(); - - glActiveTexture(GL_TEXTURE0); - glGenTextures(1, &layer->texture); - - return layer; -} - Layer* LayerRenderer::createLayer(uint32_t width, uint32_t height, bool isOpaque) { LAYER_RENDERER_LOGD("Creating new layer %dx%d", width, height); @@ -269,6 +245,41 @@ bool LayerRenderer::resizeLayer(Layer* layer, uint32_t width, uint32_t height) { return true; } +static void setTextureParameters(Layer* layer) { + glBindTexture(layer->renderTarget, layer->texture); + + glTexParameteri(layer->renderTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(layer->renderTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + glTexParameteri(layer->renderTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(layer->renderTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +} + +Layer* LayerRenderer::createTextureLayer(bool isOpaque) { + LAYER_RENDERER_LOGD("Creating new texture layer"); + + Layer* layer = new Layer(0, 0); + layer->isCacheable = false; + layer->isTextureLayer = true; + layer->blend = !isOpaque; + layer->empty = true; + layer->fbo = 0; + layer->colorFilter = NULL; + layer->fbo = 0; + layer->layer.set(0.0f, 0.0f, 0.0f, 0.0f); + layer->texCoords.set(0.0f, 1.0f, 0.0f, 1.0f); + layer->alpha = 255; + layer->mode = SkXfermode::kSrcOver_Mode; + layer->colorFilter = NULL; + layer->region.clear(); + layer->renderTarget = GL_NONE; // see ::updateTextureLayer() + + glActiveTexture(GL_TEXTURE0); + glGenTextures(1, &layer->texture); + + return layer; +} + void LayerRenderer::updateTextureLayer(Layer* layer, uint32_t width, uint32_t height, bool isOpaque, GLenum renderTarget, float* transform) { if (layer) { @@ -279,15 +290,11 @@ void LayerRenderer::updateTextureLayer(Layer* layer, uint32_t width, uint32_t he layer->region.set(width, height); layer->regionRect.set(0.0f, 0.0f, width, height); layer->texTransform.load(transform); - layer->renderTarget = renderTarget; - glBindTexture(layer->renderTarget, layer->texture); - - glTexParameteri(layer->renderTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(layer->renderTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - glTexParameteri(layer->renderTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(layer->renderTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + if (renderTarget != layer->renderTarget) { + layer->renderTarget = renderTarget; + setTextureParameters(layer); + } } } diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index dd4511163c9e..6f425966d592 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -16,17 +16,16 @@ package android.media; -import android.media.CamcorderProfile; import android.hardware.Camera; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Log; import android.view.Surface; -import java.io.IOException; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; + import java.io.FileDescriptor; +import java.io.FileOutputStream; +import java.io.IOException; import java.lang.ref.WeakReference; /** @@ -112,7 +111,8 @@ public class MediaRecorder /** * Sets a Camera to use for recording. Use this function to switch * quickly between preview and capture mode without a teardown of - * the camera object. Must call before prepare(). + * the camera object. {@link android.hardware.Camera#unlock()} should be + * called before this. Must call before prepare(). * * @param c the Camera to use for recording */ @@ -718,6 +718,11 @@ public class MediaRecorder * Begins capturing and encoding data to the file specified with * setOutputFile(). Call this after prepare(). * + * <p>Since API level 13, if applications set a camera via + * {@link #setCamera(Camera)}, the apps can use the camera after this method + * call. The apps do not need to lock the camera again. The apps should not + * start another recording session during recording. + * * @throws IllegalStateException if it is called before * prepare(). */ diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp index 2f7d7ee121ea..12391c87171e 100644 --- a/media/jni/android_media_MediaRecorder.cpp +++ b/media/jni/android_media_MediaRecorder.cpp @@ -158,7 +158,7 @@ static void android_media_MediaRecorder_setCamera(JNIEnv* env, jobject thiz, job } sp<Camera> c = get_native_camera(env, camera, NULL); sp<MediaRecorder> mr = getMediaRecorder(env, thiz); - process_media_recorder_call(env, mr->setCamera(c->remote()), + process_media_recorder_call(env, mr->setCamera(c->remote(), c->getRecordingProxy()), "java/lang/RuntimeException", "setCamera failed."); } diff --git a/media/libmedia/IMediaRecorder.cpp b/media/libmedia/IMediaRecorder.cpp index 59cd1b7b3321..a44ef5ab1b95 100644 --- a/media/libmedia/IMediaRecorder.cpp +++ b/media/libmedia/IMediaRecorder.cpp @@ -60,12 +60,13 @@ public: { } - status_t setCamera(const sp<ICamera>& camera) + status_t setCamera(const sp<ICamera>& camera, const sp<ICameraRecordingProxy>& proxy) { - LOGV("setCamera(%p)", camera.get()); + LOGV("setCamera(%p,%p)", camera.get(), proxy.get()); Parcel data, reply; data.writeInterfaceToken(IMediaRecorder::getInterfaceDescriptor()); data.writeStrongBinder(camera->asBinder()); + data.writeStrongBinder(proxy->asBinder()); remote()->transact(SET_CAMERA, data, &reply); return reply.readInt32(); } @@ -434,7 +435,9 @@ status_t BnMediaRecorder::onTransact( LOGV("SET_CAMERA"); CHECK_INTERFACE(IMediaRecorder, data, reply); sp<ICamera> camera = interface_cast<ICamera>(data.readStrongBinder()); - reply->writeInt32(setCamera(camera)); + sp<ICameraRecordingProxy> proxy = + interface_cast<ICameraRecordingProxy>(data.readStrongBinder()); + reply->writeInt32(setCamera(camera, proxy)); return NO_ERROR; } break; default: diff --git a/media/libmedia/mediarecorder.cpp b/media/libmedia/mediarecorder.cpp index 0100a1755327..9e4edd09c8c9 100644 --- a/media/libmedia/mediarecorder.cpp +++ b/media/libmedia/mediarecorder.cpp @@ -28,9 +28,9 @@ namespace android { -status_t MediaRecorder::setCamera(const sp<ICamera>& camera) +status_t MediaRecorder::setCamera(const sp<ICamera>& camera, const sp<ICameraRecordingProxy>& proxy) { - LOGV("setCamera(%p)", camera.get()); + LOGV("setCamera(%p,%p)", camera.get(), proxy.get()); if(mMediaRecorder == NULL) { LOGE("media recorder is not initialized yet"); return INVALID_OPERATION; @@ -40,7 +40,7 @@ status_t MediaRecorder::setCamera(const sp<ICamera>& camera) return INVALID_OPERATION; } - status_t ret = mMediaRecorder->setCamera(camera); + status_t ret = mMediaRecorder->setCamera(camera, proxy); if (OK != ret) { LOGV("setCamera failed: %d", ret); mCurrentState = MEDIA_RECORDER_ERROR; diff --git a/media/libmediaplayerservice/MediaRecorderClient.cpp b/media/libmediaplayerservice/MediaRecorderClient.cpp index 29cc019611ff..115db1ad2904 100644 --- a/media/libmediaplayerservice/MediaRecorderClient.cpp +++ b/media/libmediaplayerservice/MediaRecorderClient.cpp @@ -57,7 +57,8 @@ static bool checkPermission(const char* permissionString) { return ok; } -status_t MediaRecorderClient::setCamera(const sp<ICamera>& camera) +status_t MediaRecorderClient::setCamera(const sp<ICamera>& camera, + const sp<ICameraRecordingProxy>& proxy) { LOGV("setCamera"); Mutex::Autolock lock(mLock); @@ -65,7 +66,7 @@ status_t MediaRecorderClient::setCamera(const sp<ICamera>& camera) LOGE("recorder is not initialized"); return NO_INIT; } - return mRecorder->setCamera(camera); + return mRecorder->setCamera(camera, proxy); } status_t MediaRecorderClient::setPreviewSurface(const sp<Surface>& surface) diff --git a/media/libmediaplayerservice/MediaRecorderClient.h b/media/libmediaplayerservice/MediaRecorderClient.h index fded98e22beb..bbca5291cd5c 100644 --- a/media/libmediaplayerservice/MediaRecorderClient.h +++ b/media/libmediaplayerservice/MediaRecorderClient.h @@ -24,11 +24,13 @@ namespace android { class MediaRecorderBase; class MediaPlayerService; +class ICameraRecordingProxy; class MediaRecorderClient : public BnMediaRecorder { public: - virtual status_t setCamera(const sp<ICamera>& camera); + virtual status_t setCamera(const sp<ICamera>& camera, + const sp<ICameraRecordingProxy>& proxy); virtual status_t setPreviewSurface(const sp<Surface>& surface); virtual status_t setVideoSource(int vs); virtual status_t setAudioSource(int as); diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp index 978571c0166b..b003476478e9 100644 --- a/media/libmediaplayerservice/StagefrightRecorder.cpp +++ b/media/libmediaplayerservice/StagefrightRecorder.cpp @@ -198,14 +198,20 @@ status_t StagefrightRecorder::setVideoFrameRate(int frames_per_second) { return OK; } -status_t StagefrightRecorder::setCamera(const sp<ICamera> &camera) { +status_t StagefrightRecorder::setCamera(const sp<ICamera> &camera, + const sp<ICameraRecordingProxy> &proxy) { LOGV("setCamera"); if (camera == 0) { LOGE("camera is NULL"); return BAD_VALUE; } + if (proxy == 0) { + LOGE("camera proxy is NULL"); + return BAD_VALUE; + } mCamera = camera; + mCameraProxy = proxy; return OK; } @@ -1235,15 +1241,17 @@ status_t StagefrightRecorder::setupCameraSource( videoSize.height = mVideoHeight; if (mCaptureTimeLapse) { mCameraSourceTimeLapse = CameraSourceTimeLapse::CreateFromCamera( - mCamera, mCameraId, + mCamera, mCameraProxy, mCameraId, videoSize, mFrameRate, mPreviewSurface, mTimeBetweenTimeLapseFrameCaptureUs); *cameraSource = mCameraSourceTimeLapse; } else { *cameraSource = CameraSource::CreateFromCamera( - mCamera, mCameraId, videoSize, mFrameRate, + mCamera, mCameraProxy, mCameraId, videoSize, mFrameRate, mPreviewSurface, true /*storeMetaDataInVideoBuffers*/); } + mCamera.clear(); + mCameraProxy.clear(); if (*cameraSource == NULL) { return UNKNOWN_ERROR; } diff --git a/media/libmediaplayerservice/StagefrightRecorder.h b/media/libmediaplayerservice/StagefrightRecorder.h index aa67aa766a08..cb9c4062a94f 100644 --- a/media/libmediaplayerservice/StagefrightRecorder.h +++ b/media/libmediaplayerservice/StagefrightRecorder.h @@ -27,6 +27,7 @@ namespace android { class Camera; +class ICameraRecordingProxy; class CameraSource; class CameraSourceTimeLapse; class MediaSourceSplitter; @@ -48,7 +49,7 @@ struct StagefrightRecorder : public MediaRecorderBase { virtual status_t setVideoEncoder(video_encoder ve); virtual status_t setVideoSize(int width, int height); virtual status_t setVideoFrameRate(int frames_per_second); - virtual status_t setCamera(const sp<ICamera>& camera); + virtual status_t setCamera(const sp<ICamera>& camera, const sp<ICameraRecordingProxy>& proxy); virtual status_t setPreviewSurface(const sp<Surface>& surface); virtual status_t setOutputFile(const char *path); virtual status_t setOutputFile(int fd, int64_t offset, int64_t length); @@ -66,6 +67,7 @@ struct StagefrightRecorder : public MediaRecorderBase { private: sp<ICamera> mCamera; + sp<ICameraRecordingProxy> mCameraProxy; sp<Surface> mPreviewSurface; sp<IMediaRecorderClient> mListener; sp<MediaWriter> mWriter, mWriterAux; diff --git a/media/libstagefright/CameraSource.cpp b/media/libstagefright/CameraSource.cpp index 61bb2a89b44f..bdffce9c10fe 100644 --- a/media/libstagefright/CameraSource.cpp +++ b/media/libstagefright/CameraSource.cpp @@ -115,19 +115,20 @@ CameraSource *CameraSource::Create() { size.height = -1; sp<ICamera> camera; - return new CameraSource(camera, 0, size, -1, NULL, false); + return new CameraSource(camera, NULL, 0, size, -1, NULL, false); } // static CameraSource *CameraSource::CreateFromCamera( const sp<ICamera>& camera, + const sp<ICameraRecordingProxy>& proxy, int32_t cameraId, Size videoSize, int32_t frameRate, const sp<Surface>& surface, bool storeMetaDataInVideoBuffers) { - CameraSource *source = new CameraSource(camera, cameraId, + CameraSource *source = new CameraSource(camera, proxy, cameraId, videoSize, frameRate, surface, storeMetaDataInVideoBuffers); return source; @@ -135,6 +136,7 @@ CameraSource *CameraSource::CreateFromCamera( CameraSource::CameraSource( const sp<ICamera>& camera, + const sp<ICameraRecordingProxy>& proxy, int32_t cameraId, Size videoSize, int32_t frameRate, @@ -153,11 +155,10 @@ CameraSource::CameraSource( mNumGlitches(0), mGlitchDurationThresholdUs(200000), mCollectStats(false) { - mVideoSize.width = -1; mVideoSize.height = -1; - mInitCheck = init(camera, cameraId, + mInitCheck = init(camera, proxy, cameraId, videoSize, frameRate, storeMetaDataInVideoBuffers); } @@ -167,24 +168,32 @@ status_t CameraSource::initCheck() const { } status_t CameraSource::isCameraAvailable( - const sp<ICamera>& camera, int32_t cameraId) { + const sp<ICamera>& camera, const sp<ICameraRecordingProxy>& proxy, + int32_t cameraId) { if (camera == 0) { mCamera = Camera::connect(cameraId); + if (mCamera == 0) return -EBUSY; + // If proxy is not passed in by applications, still use the proxy of + // our own Camera to simplify the code. + mCameraRecordingProxy = mCamera->getRecordingProxy(); mCameraFlags &= ~FLAGS_HOT_CAMERA; } else { + // We get the proxy from Camera, not ICamera. We need to get the proxy + // to the remote Camera owned by the application. Here mCamera is a + // local Camera object created by us. We cannot use the proxy from + // mCamera here. mCamera = Camera::create(camera); + if (mCamera == 0) return -EBUSY; + mCameraRecordingProxy = proxy; mCameraFlags |= FLAGS_HOT_CAMERA; } - // Is camera available? - if (mCamera == 0) { - LOGE("Camera connection could not be established."); - return -EBUSY; - } - if (!(mCameraFlags & FLAGS_HOT_CAMERA)) { - mCamera->lock(); - } + mCamera->lock(); + mDeathNotifier = new DeathNotifier(); + // isBinderAlive needs linkToDeath to work. + mCameraRecordingProxy->asBinder()->linkToDeath(mDeathNotifier); + return OK; } @@ -447,6 +456,7 @@ status_t CameraSource::checkFrameRate( */ status_t CameraSource::init( const sp<ICamera>& camera, + const sp<ICameraRecordingProxy>& proxy, int32_t cameraId, Size videoSize, int32_t frameRate, @@ -455,7 +465,8 @@ status_t CameraSource::init( status_t err = OK; int64_t token = IPCThreadState::self()->clearCallingIdentity(); - if ((err = isCameraAvailable(camera, cameraId)) != OK) { + if ((err = isCameraAvailable(camera, proxy, cameraId)) != OK) { + LOGE("Camera connection could not be established."); return err; } CameraParameters params(mCamera->getParameters()); @@ -521,8 +532,14 @@ CameraSource::~CameraSource() { } void CameraSource::startCameraRecording() { - CHECK_EQ(OK, mCamera->startRecording()); - CHECK(mCamera->recordingEnabled()); + // Reset the identity to the current thread because media server owns the + // camera and recording is started by the applications. The applications + // will connect to the camera in ICameraRecordingProxy::startRecording. + int64_t token = IPCThreadState::self()->clearCallingIdentity(); + mCamera->unlock(); + mCamera.clear(); + IPCThreadState::self()->restoreCallingIdentity(token); + CHECK_EQ(OK, mCameraRecordingProxy->startRecording(new ProxyListener(this))); } status_t CameraSource::start(MetaData *meta) { @@ -544,20 +561,14 @@ status_t CameraSource::start(MetaData *meta) { mStartTimeUs = startTimeUs; } - // Call setListener first before calling startCameraRecording() - // to avoid recording frames being dropped. - int64_t token = IPCThreadState::self()->clearCallingIdentity(); - mCamera->setListener(new CameraSourceListener(this)); startCameraRecording(); - IPCThreadState::self()->restoreCallingIdentity(token); mStarted = true; return OK; } void CameraSource::stopCameraRecording() { - mCamera->setListener(NULL); - mCamera->stopRecording(); + mCameraRecordingProxy->stopRecording(); } void CameraSource::releaseCamera() { @@ -566,9 +577,9 @@ void CameraSource::releaseCamera() { LOGV("Camera was cold when we started, stopping preview"); mCamera->stopPreview(); } - mCamera->unlock(); mCamera.clear(); - mCamera = 0; + mCameraRecordingProxy->asBinder()->unlinkToDeath(mDeathNotifier); + mCameraRecordingProxy.clear(); mCameraFlags = 0; } @@ -578,7 +589,6 @@ status_t CameraSource::stop() { mStarted = false; mFrameAvailableCondition.signal(); - int64_t token = IPCThreadState::self()->clearCallingIdentity(); releaseQueuedFrames(); while (!mFramesBeingEncoded.empty()) { if (NO_ERROR != @@ -589,7 +599,6 @@ status_t CameraSource::stop() { } stopCameraRecording(); releaseCamera(); - IPCThreadState::self()->restoreCallingIdentity(token); if (mCollectStats) { LOGI("Frames received/encoded/dropped: %d/%d/%d in %lld us", @@ -607,8 +616,8 @@ status_t CameraSource::stop() { } void CameraSource::releaseRecordingFrame(const sp<IMemory>& frame) { - if (mCamera != NULL) { - mCamera->releaseRecordingFrame(frame); + if (mCameraRecordingProxy != NULL) { + mCameraRecordingProxy->releaseRecordingFrame(frame); } } @@ -627,9 +636,7 @@ sp<MetaData> CameraSource::getFormat() { } void CameraSource::releaseOneRecordingFrame(const sp<IMemory>& frame) { - int64_t token = IPCThreadState::self()->clearCallingIdentity(); releaseRecordingFrame(frame); - IPCThreadState::self()->restoreCallingIdentity(token); } void CameraSource::signalBufferReturned(MediaBuffer *buffer) { @@ -669,7 +676,11 @@ status_t CameraSource::read( Mutex::Autolock autoLock(mLock); while (mStarted && mFramesReceived.empty()) { if (NO_ERROR != - mFrameAvailableCondition.waitRelative(mLock, 3000000000LL)) { + mFrameAvailableCondition.waitRelative(mLock, 1000000000LL)) { + if (!mCameraRecordingProxy->asBinder()->isBinderAlive()) { + LOGW("camera recording proxy is gone"); + return ERROR_END_OF_STREAM; + } LOGW("Timed out waiting for incoming camera video frames: %lld us", mLastFrameTimestampUs); } @@ -745,4 +756,17 @@ bool CameraSource::isMetaDataStoredInVideoBuffers() const { return mIsMetaDataStoredInVideoBuffers; } +CameraSource::ProxyListener::ProxyListener(const sp<CameraSource>& source) { + mSource = source; +} + +void CameraSource::ProxyListener::dataCallbackTimestamp( + nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr) { + mSource->dataCallbackTimestamp(timestamp / 1000, msgType, dataPtr); +} + +void CameraSource::DeathNotifier::binderDied(const wp<IBinder>& who) { + LOGI("Camera recording proxy died"); +} + } // namespace android diff --git a/media/libstagefright/CameraSourceTimeLapse.cpp b/media/libstagefright/CameraSourceTimeLapse.cpp index cc2257477071..fe78c462d1f5 100644 --- a/media/libstagefright/CameraSourceTimeLapse.cpp +++ b/media/libstagefright/CameraSourceTimeLapse.cpp @@ -39,6 +39,7 @@ namespace android { // static CameraSourceTimeLapse *CameraSourceTimeLapse::CreateFromCamera( const sp<ICamera> &camera, + const sp<ICameraRecordingProxy> &proxy, int32_t cameraId, Size videoSize, int32_t videoFrameRate, @@ -46,7 +47,7 @@ CameraSourceTimeLapse *CameraSourceTimeLapse::CreateFromCamera( int64_t timeBetweenTimeLapseFrameCaptureUs) { CameraSourceTimeLapse *source = new - CameraSourceTimeLapse(camera, cameraId, + CameraSourceTimeLapse(camera, proxy, cameraId, videoSize, videoFrameRate, surface, timeBetweenTimeLapseFrameCaptureUs); @@ -61,12 +62,13 @@ CameraSourceTimeLapse *CameraSourceTimeLapse::CreateFromCamera( CameraSourceTimeLapse::CameraSourceTimeLapse( const sp<ICamera>& camera, + const sp<ICameraRecordingProxy>& proxy, int32_t cameraId, Size videoSize, int32_t videoFrameRate, const sp<Surface>& surface, int64_t timeBetweenTimeLapseFrameCaptureUs) - : CameraSource(camera, cameraId, videoSize, videoFrameRate, surface, true), + : CameraSource(camera, proxy, cameraId, videoSize, videoFrameRate, surface, true), mTimeBetweenTimeLapseFrameCaptureUs(timeBetweenTimeLapseFrameCaptureUs), mTimeBetweenTimeLapseVideoFramesUs(1E6/videoFrameRate), mLastTimeLapseFrameRealTimestampUs(0), @@ -315,7 +317,7 @@ void CameraSourceTimeLapse::startCameraRecording() { pthread_attr_destroy(&attr); } else { LOGV("start time lapse recording using video camera"); - CHECK_EQ(OK, mCamera->startRecording()); + CameraSource::startCameraRecording(); } } @@ -337,8 +339,7 @@ void CameraSourceTimeLapse::stopCameraRecording() { // play the recording sound. mCamera->sendCommand(CAMERA_CMD_PLAY_RECORDING_SOUND, 0, 0); } else { - mCamera->setListener(NULL); - mCamera->stopRecording(); + CameraSource::stopCameraRecording(); } if (mLastReadBufferCopy) { mLastReadBufferCopy->release(); @@ -347,9 +348,8 @@ void CameraSourceTimeLapse::stopCameraRecording() { } void CameraSourceTimeLapse::releaseRecordingFrame(const sp<IMemory>& frame) { - if (!mUseStillCameraForTimeLapse && - mCamera != NULL) { - mCamera->releaseRecordingFrame(frame); + if (!mUseStillCameraForTimeLapse) { + CameraSource::releaseRecordingFrame(frame); } } diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp index 314425f8a241..cd97302c290b 100644 --- a/media/libstagefright/OMXCodec.cpp +++ b/media/libstagefright/OMXCodec.cpp @@ -4216,6 +4216,10 @@ void OMXCodec::initOutputFormat(const sp<MetaData> &inputFormat) { mNode, OMX_IndexConfigCommonOutputCrop, &rect, sizeof(rect)); + CODEC_LOGI( + "video dimensions are %ld x %ld", + video_def->nFrameWidth, video_def->nFrameHeight); + if (err == OK) { CHECK_GE(rect.nLeft, 0); CHECK_GE(rect.nTop, 0); @@ -4230,6 +4234,10 @@ void OMXCodec::initOutputFormat(const sp<MetaData> &inputFormat) { rect.nTop, rect.nLeft + rect.nWidth - 1, rect.nTop + rect.nHeight - 1); + + CODEC_LOGI( + "Crop rect is %ld x %ld @ (%ld, %ld)", + rect.nWidth, rect.nHeight, rect.nLeft, rect.nTop); } else { mOutputFormat->setRect( kKeyCropRect, @@ -4238,7 +4246,6 @@ void OMXCodec::initOutputFormat(const sp<MetaData> &inputFormat) { video_def->nFrameHeight - 1); } } - break; } diff --git a/media/libstagefright/codecs/on2/h264dec/SoftAVC.cpp b/media/libstagefright/codecs/on2/h264dec/SoftAVC.cpp index 830d2e018b2a..ec7bd1cd3a55 100644 --- a/media/libstagefright/codecs/on2/h264dec/SoftAVC.cpp +++ b/media/libstagefright/codecs/on2/h264dec/SoftAVC.cpp @@ -139,7 +139,8 @@ void SoftAVC::initPorts() { } status_t SoftAVC::initDecoder() { - if (H264SwDecInit(&mHandle, 1) == H264SWDEC_OK) { + // Force decoder to output buffers in display order. + if (H264SwDecInit(&mHandle, 0) == H264SWDEC_OK) { return OK; } return UNKNOWN_ERROR; diff --git a/media/libstagefright/codecs/on2/h264dec/SoftAVC.h b/media/libstagefright/codecs/on2/h264dec/SoftAVC.h index 3439efdf8b8a..1cc85e89e187 100644 --- a/media/libstagefright/codecs/on2/h264dec/SoftAVC.h +++ b/media/libstagefright/codecs/on2/h264dec/SoftAVC.h @@ -52,7 +52,7 @@ private: kInputPortIndex = 0, kOutputPortIndex = 1, kNumInputBuffers = 8, - kNumOutputBuffers = 16, + kNumOutputBuffers = 2, }; enum EOSStatus { diff --git a/media/libstagefright/colorconversion/ColorConverter.cpp b/media/libstagefright/colorconversion/ColorConverter.cpp index fd933ccfe5cf..5cc3f78804ab 100644 --- a/media/libstagefright/colorconversion/ColorConverter.cpp +++ b/media/libstagefright/colorconversion/ColorConverter.cpp @@ -14,6 +14,10 @@ * limitations under the License. */ +//#define LOG_NDEBUG 0 +#define LOG_TAG "ColorConverter" +#include <utils/Log.h> + #include <media/stagefright/ColorConverter.h> #include <media/stagefright/MediaDebug.h> #include <media/stagefright/MediaErrors.h> @@ -424,61 +428,30 @@ status_t ColorConverter::convertYUV420SemiPlanar( status_t ColorConverter::convertTIYUV420PackedSemiPlanar( const BitmapParams &src, const BitmapParams &dst) { + uint8_t *kAdjustedClip = initClip(); -/* -The TIYUV420PackedSemiPlanar format is same as YUV420PackedSemiPlanar but with -additional padding as shown in the diagram below. The padded width and padded -height is different for different compression formats and it is read from the -codec. In this color conversion routine, the padded resolution is obtained from -src bitmap. - - ------------------------------------ -| | -| | -| ------------------------- | -| | | | -| | | | -| | Y DATA | | -| | | | -| | | | -| | | | -| ------------------------- | -| | -| ------------ | -| | | | -| | | | -| | UV DATA | | -| | | | -| | | | -| | | | -| ------------ | -| | -| | - ------------------------------------ -*/ - - LOGV("src.mCropLeft = %d src.mCropTop =%d src.mWidth = %d src.mHeight = %d" - " dst.mWidth = %d dst.mHeight = %d", src.mCropLeft , src.mCropTop, - src.mWidth, src.mHeight, dst.mWidth, dst.mHeight); - - size_t offset = (src.mWidth * src.mCropTop) + src.mCropLeft; + if (!((dst.mWidth & 3) == 0 + && (src.mCropLeft & 1) == 0 + && src.cropWidth() == dst.cropWidth() + && src.cropHeight() == dst.cropHeight())) { + return ERROR_UNSUPPORTED; + } - uint8_t *kAdjustedClip = initClip(); + uint32_t *dst_ptr = (uint32_t *)dst.mBits + + (dst.mCropTop * dst.mWidth + dst.mCropLeft) / 2; - uint32_t *dst_ptr = (uint32_t *)dst.mBits; const uint8_t *src_y = (const uint8_t *)src.mBits; - const uint8_t *src_u = (const uint8_t *)(src_y-offset) + (src.mWidth * src.mHeight); - src_u += ( ( src.mWidth * (src.mCropTop/2) ) + src.mCropLeft ); - const uint8_t *src_v = src_u + 1; - for (size_t y = 0; y < dst.mHeight; ++y) { - for (size_t x = 0; x < dst.mWidth; x += 2) { + const uint8_t *src_u = + (const uint8_t *)src_y + src.mWidth * (src.mHeight - src.mCropTop / 2); - signed y1 = (signed)src_y[x] - 16; //Y pixel - signed y2 = (signed)src_y[x + 1] - 16; //2nd Y pixel + for (size_t y = 0; y < src.cropHeight(); ++y) { + for (size_t x = 0; x < src.cropWidth(); x += 2) { + signed y1 = (signed)src_y[x] - 16; + signed y2 = (signed)src_y[x + 1] - 16; - signed u = (signed)src_u[x & ~1] - 128; //U component - signed v = (signed)src_u[(x & ~1) + 1] - 128; //V component + signed u = (signed)src_u[x & ~1] - 128; + signed v = (signed)src_u[(x & ~1) + 1] - 128; signed u_b = u * 517; signed u_g = -u * 100; @@ -502,19 +475,21 @@ src bitmap. uint32_t rgb2 = ((kAdjustedClip[r2] >> 3) << 11) - | ((kAdjustedClip[g1] >> 2) << 5) - | (kAdjustedClip[b1] >> 3); + | ((kAdjustedClip[g2] >> 2) << 5) + | (kAdjustedClip[b2] >> 3); dst_ptr[x / 2] = (rgb2 << 16) | rgb1; } - src_y += src.mWidth; //increment Y-pixel line - if (y&1) { - src_u += src.mWidth; //increment U-V line + src_y += src.mWidth; + + if (y & 1) { + src_u += src.mWidth; } dst_ptr += dst.mWidth / 2; } + return OK; } diff --git a/media/libstagefright/colorconversion/SoftwareRenderer.cpp b/media/libstagefright/colorconversion/SoftwareRenderer.cpp index a4e8ee4846a1..a4ca32d70d79 100644 --- a/media/libstagefright/colorconversion/SoftwareRenderer.cpp +++ b/media/libstagefright/colorconversion/SoftwareRenderer.cpp @@ -50,6 +50,9 @@ SoftwareRenderer::SoftwareRenderer( mCropBottom = mHeight - 1; } + mCropWidth = mCropRight - mCropLeft + 1; + mCropHeight = mCropBottom - mCropTop + 1; + int32_t rotationDegrees; if (!meta->findInt32(kKeyRotation, &rotationDegrees)) { rotationDegrees = 0; @@ -60,17 +63,18 @@ SoftwareRenderer::SoftwareRenderer( switch (mColorFormat) { case OMX_COLOR_FormatYUV420Planar: + case OMX_TI_COLOR_FormatYUV420PackedSemiPlanar: { halFormat = HAL_PIXEL_FORMAT_YV12; - bufWidth = (mWidth + 1) & ~1; - bufHeight = (mHeight + 1) & ~1; + bufWidth = (mCropWidth + 1) & ~1; + bufHeight = (mCropHeight + 1) & ~1; break; } default: halFormat = HAL_PIXEL_FORMAT_RGB_565; - bufWidth = mWidth; - bufHeight = mHeight; + bufWidth = mCropWidth; + bufHeight = mCropHeight; mConverter = new ColorConverter( mColorFormat, OMX_COLOR_Format16bitRGB565); @@ -79,8 +83,8 @@ SoftwareRenderer::SoftwareRenderer( } CHECK(mNativeWindow != NULL); - CHECK(mWidth > 0); - CHECK(mHeight > 0); + CHECK(mCropWidth > 0); + CHECK(mCropHeight > 0); CHECK(mConverter == NULL || mConverter->isValid()); CHECK_EQ(0, @@ -109,14 +113,6 @@ SoftwareRenderer::SoftwareRenderer( CHECK_EQ(0, native_window_set_buffers_transform( mNativeWindow.get(), transform)); } - - android_native_rect_t crop; - crop.left = mCropLeft; - crop.top = mCropTop; - crop.right = mCropRight + 1; - crop.bottom = mCropBottom + 1; - - CHECK_EQ(0, native_window_set_crop(mNativeWindow.get(), &crop)); } SoftwareRenderer::~SoftwareRenderer() { @@ -142,7 +138,7 @@ void SoftwareRenderer::render( GraphicBufferMapper &mapper = GraphicBufferMapper::get(); - Rect bounds(mWidth, mHeight); + Rect bounds(mCropWidth, mCropHeight); void *dst; CHECK_EQ(0, mapper.lock( @@ -152,13 +148,11 @@ void SoftwareRenderer::render( mConverter->convert( data, mWidth, mHeight, - 0, 0, mWidth - 1, mHeight - 1, + mCropLeft, mCropTop, mCropRight, mCropBottom, dst, buf->stride, buf->height, - 0, 0, mWidth - 1, mHeight - 1); - } else { - CHECK_EQ(mColorFormat, OMX_COLOR_FormatYUV420Planar); - + 0, 0, mCropWidth - 1, mCropHeight - 1); + } else if (mColorFormat == OMX_COLOR_FormatYUV420Planar) { const uint8_t *src_y = (const uint8_t *)data; const uint8_t *src_u = (const uint8_t *)data + mWidth * mHeight; const uint8_t *src_v = src_u + (mWidth / 2 * mHeight / 2); @@ -170,22 +164,57 @@ void SoftwareRenderer::render( uint8_t *dst_v = dst_y + dst_y_size; uint8_t *dst_u = dst_v + dst_c_size; - for (int y = 0; y < mHeight; ++y) { - memcpy(dst_y, src_y, mWidth); + for (int y = 0; y < mCropHeight; ++y) { + memcpy(dst_y, src_y, mCropWidth); src_y += mWidth; dst_y += buf->stride; } - for (int y = 0; y < (mHeight + 1) / 2; ++y) { - memcpy(dst_u, src_u, (mWidth + 1) / 2); - memcpy(dst_v, src_v, (mWidth + 1) / 2); + for (int y = 0; y < (mCropHeight + 1) / 2; ++y) { + memcpy(dst_u, src_u, (mCropWidth + 1) / 2); + memcpy(dst_v, src_v, (mCropWidth + 1) / 2); src_u += mWidth / 2; src_v += mWidth / 2; dst_u += dst_c_stride; dst_v += dst_c_stride; } + } else { + CHECK_EQ(mColorFormat, OMX_TI_COLOR_FormatYUV420PackedSemiPlanar); + + const uint8_t *src_y = + (const uint8_t *)data; + + const uint8_t *src_uv = + (const uint8_t *)data + mWidth * (mHeight - mCropTop / 2); + + uint8_t *dst_y = (uint8_t *)dst; + + size_t dst_y_size = buf->stride * buf->height; + size_t dst_c_stride = ALIGN(buf->stride / 2, 16); + size_t dst_c_size = dst_c_stride * buf->height / 2; + uint8_t *dst_v = dst_y + dst_y_size; + uint8_t *dst_u = dst_v + dst_c_size; + + for (int y = 0; y < mCropHeight; ++y) { + memcpy(dst_y, src_y, mCropWidth); + + src_y += mWidth; + dst_y += buf->stride; + } + + for (int y = 0; y < (mCropHeight + 1) / 2; ++y) { + size_t tmp = (mCropWidth + 1) / 2; + for (size_t x = 0; x < tmp; ++x) { + dst_u[x] = src_uv[2 * x]; + dst_v[x] = src_uv[2 * x + 1]; + } + + src_uv += mWidth; + dst_u += dst_c_stride; + dst_v += dst_c_stride; + } } CHECK_EQ(0, mapper.unlock(buf->handle)); diff --git a/media/libstagefright/include/SoftwareRenderer.h b/media/libstagefright/include/SoftwareRenderer.h index 78037b97a2f3..8f2ea9503f21 100644 --- a/media/libstagefright/include/SoftwareRenderer.h +++ b/media/libstagefright/include/SoftwareRenderer.h @@ -47,6 +47,7 @@ private: sp<ANativeWindow> mNativeWindow; int32_t mWidth, mHeight; int32_t mCropLeft, mCropTop, mCropRight, mCropBottom; + int32_t mCropWidth, mCropHeight; SoftwareRenderer(const SoftwareRenderer &); SoftwareRenderer &operator=(const SoftwareRenderer &); diff --git a/media/libstagefright/omx/tests/OMXHarness.cpp b/media/libstagefright/omx/tests/OMXHarness.cpp index a404f1fa7fcd..d4354dba2587 100644 --- a/media/libstagefright/omx/tests/OMXHarness.cpp +++ b/media/libstagefright/omx/tests/OMXHarness.cpp @@ -485,24 +485,20 @@ static const char *GetURLForMime(const char *mime) { }; static const MimeToURL kMimeToURL[] = { { "video/avc", - "file:///sdcard/media_api/video/H264_AAC.3gp" }, - { "video/mp4v-es", "file:///sdcard/media_api/video/gingerkids.MP4" }, + "file:///sdcard/media_api/video/H264_500_AAC_128.3gp" }, + { "video/mp4v-es", "file:///sdcard/media_api/video/MPEG4_320_AAC_64.mp4" }, { "video/3gpp", "file:///sdcard/media_api/video/H263_500_AMRNB_12.3gp" }, { "audio/3gpp", "file:///sdcard/media_api/video/H263_500_AMRNB_12.3gp" }, - { "audio/amr-wb", - "file:///sdcard/media_api/music/" - "AI_AMR-WB_12.65kbps(13kbps)_16khz_mono_NMC.awb" }, + { "audio/amr-wb", NULL }, { "audio/mp4a-latm", - "file:///sdcard/media_api/video/H264_AAC.3gp" }, + "file:///sdcard/media_api/video/H263_56_AAC_24.3gp" }, { "audio/mpeg", - "file:///sdcard/media_api/music/MP3CBR.mp3" }, - { "audio/vorbis", - "file:///sdcard/media_api/metaDataTestMedias/OGG/" - "When You Say Nothing At All.ogg" }, + "file:///sdcard/media_api/music/MP3_48KHz_128kbps_s_1_17_CBR.mp3" }, + { "audio/vorbis", NULL }, { "video/x-vnd.on2.vp8", - "file:///sdcard/media_api/webm/big-buck-bunny_trailer.webm" }, + "file:///sdcard/media_api/video/big-buck-bunny_trailer.webm" }, { MEDIA_MIMETYPE_AUDIO_G711_ALAW, "file:///sdcard/M1F1-Alaw-AFsp.wav" }, { MEDIA_MIMETYPE_AUDIO_G711_MLAW, "file:///sdcard/M1F1-mulaw-AFsp.wav" }, diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/VideoEditorAPITest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/VideoEditorAPITest.java index 0dadaa56ca8a..5120694128a9 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/VideoEditorAPITest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/VideoEditorAPITest.java @@ -1334,7 +1334,14 @@ public class VideoEditorAPITest extends // TODO : remove TC_API_035 @LargeTest public void testEffectKenBurn() throws Exception { - final String imageItemFileName = INPUT_FILE_PATH + "IMG_640x480.jpg"; + // Test ken burn effect using a JPEG file. + testEffectKenBurn(INPUT_FILE_PATH + "IMG_640x480.jpg"); + + // Test ken burn effect using a PNG file + testEffectKenBurn(INPUT_FILE_PATH + "IMG_640x480.png"); + } + + private void testEffectKenBurn(final String imageItemFileName) throws Exception { final int imageItemRenderingMode =MediaItem.RENDERING_MODE_BLACK_BORDER; final MediaImageItem mediaImageItem = mVideoEditorHelper.createMediaItem(mVideoEditor, "mediaImageItem1", @@ -1350,11 +1357,14 @@ public class VideoEditorAPITest extends final EffectKenBurns kbEffectOnMediaItem = new EffectKenBurns( mediaImageItem, "KBOnM2", startRect, endRect, 500, 3000); - assertNotNull("EffectKenBurns", kbEffectOnMediaItem); + assertNotNull("EffectKenBurns: " + imageItemFileName, + kbEffectOnMediaItem); + mediaImageItem.addEffect(kbEffectOnMediaItem); - assertEquals("KenBurn Start Rect", startRect, + assertEquals("KenBurn Start Rect: " + imageItemFileName, startRect, kbEffectOnMediaItem.getStartRect()); - assertEquals("KenBurn End Rect", endRect, + + assertEquals("KenBurn End Rect: " + imageItemFileName, endRect, kbEffectOnMediaItem.getEndRect()); } diff --git a/policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java b/policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java index cd79b60f8b96..657328620cc8 100644 --- a/policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java +++ b/policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java @@ -183,8 +183,7 @@ class PatternUnlockScreen extends LinearLayoutWithDefaultTouchRecepient mStatusView = new StatusView(this, mUpdateMonitor, mLockPatternUtils); // This shows up when no other information is required on status1 - mStatusView.setHelpMessage(R.string.lockscreen_pattern_instructions, - StatusView.LOCK_ICON); + //mStatusView.setHelpMessage(R.string.lockscreen_pattern_instructions,StatusView.LOCK_ICON); mLockPatternView = (LockPatternView) findViewById(R.id.lockPattern); diff --git a/policy/src/com/android/internal/policy/impl/StatusView.java b/policy/src/com/android/internal/policy/impl/StatusView.java index 46ce5a369c5f..79f81ffe95c1 100644 --- a/policy/src/com/android/internal/policy/impl/StatusView.java +++ b/policy/src/com/android/internal/policy/impl/StatusView.java @@ -40,7 +40,7 @@ class StatusView { private String mInstructions = null; private TextView mStatus1; - private TextView mPropertyOf; + private TextView mOwnerInfo; private boolean mHasCarrier; private boolean mHasDate; @@ -105,7 +105,7 @@ class StatusView { mStatus1 = (TextView) findViewById(R.id.status1); mAlarmStatus = (TextView) findViewById(R.id.alarm_status); mAlarmStatus.setCompoundDrawablesWithIntrinsicBounds(ALARM_ICON, 0, 0, 0); - mPropertyOf = (TextView) findViewById(R.id.propertyOf); + mOwnerInfo = (TextView) findViewById(R.id.propertyOf); resetStatusInfo(updateMonitor, lockPatternUtils); @@ -153,20 +153,22 @@ class StatusView { void updateStatusLines(boolean showStatusLines) { if (!showStatusLines) { mStatus1.setVisibility(showStatusLines ? View.VISIBLE : View.INVISIBLE); - mAlarmStatus.setVisibility(showStatusLines ? View.VISIBLE : View.INVISIBLE); + mAlarmStatus.setVisibility(showStatusLines ? View.VISIBLE : View.GONE); return; } // Update owner info - if (mPropertyOf != null) { - ContentResolver res = getContext().getContentResolver(); - String info = Settings.Secure.getString(res, Settings.Secure.LOCK_SCREEN_OWNER_INFO); - boolean enabled = Settings.Secure.getInt(res, - Settings.Secure.LOCK_SCREEN_OWNER_INFO_ENABLED, 1) != 0; - - mPropertyOf.setText(info); - mPropertyOf.setVisibility(enabled && !TextUtils.isEmpty(info) ? - View.VISIBLE : View.INVISIBLE); + final ContentResolver res = getContext().getContentResolver(); + final boolean ownerInfoEnabled = Settings.Secure.getInt(res, + Settings.Secure.LOCK_SCREEN_OWNER_INFO_ENABLED, 1) != 0; + String ownerInfo = null; + if (ownerInfoEnabled) { + ownerInfo = Settings.Secure.getString(res, Settings.Secure.LOCK_SCREEN_OWNER_INFO); + if (mOwnerInfo != null) { + mOwnerInfo.setText(ownerInfo); + mOwnerInfo.setVisibility(ownerInfoEnabled && !TextUtils.isEmpty(ownerInfo) ? + View.VISIBLE : View.INVISIBLE); + } } // Update Alarm status @@ -175,7 +177,7 @@ class StatusView { mAlarmStatus.setText(nextAlarm); mAlarmStatus.setVisibility(View.VISIBLE); } else { - mAlarmStatus.setVisibility(View.INVISIBLE); + mAlarmStatus.setVisibility(View.GONE); } // Update Status1 @@ -204,15 +206,16 @@ class StatusView { } else { mStatus1.setVisibility(View.INVISIBLE); } + } else if (mHelpMessageId != 0) { + mStatus1.setText(mHelpMessageId); + mStatus1.setCompoundDrawablesWithIntrinsicBounds(mHelpIconId, 0,0, 0); + mStatus1.setVisibility(View.VISIBLE); + } else if (ownerInfoEnabled && mOwnerInfo == null && ownerInfo != null) { + mStatus1.setText(ownerInfo); + mStatus1.setCompoundDrawablesWithIntrinsicBounds(0, 0,0, 0); + mStatus1.setVisibility(View.VISIBLE); } else { - // nothing specific to show; show help message and icon, if provided - if (mHelpMessageId != 0) { - mStatus1.setText(mHelpMessageId); - mStatus1.setCompoundDrawablesWithIntrinsicBounds(mHelpIconId, 0,0, 0); - mStatus1.setVisibility(View.VISIBLE); - } else { - mStatus1.setVisibility(View.INVISIBLE); - } + mStatus1.setVisibility(View.INVISIBLE); } } diff --git a/services/camera/libcameraservice/CameraHardwareInterface.h b/services/camera/libcameraservice/CameraHardwareInterface.h index a3749cf401eb..a583aad01e9f 100644 --- a/services/camera/libcameraservice/CameraHardwareInterface.h +++ b/services/camera/libcameraservice/CameraHardwareInterface.h @@ -559,13 +559,8 @@ private: ANativeWindowBuffer* anb; rc = a->dequeueBuffer(a, &anb); if (!rc) { - rc = a->lockBuffer(a, anb); - if (!rc) { - *buffer = &anb->handle; - *stride = anb->stride; - } - else - a->cancelBuffer(a, anb); + *buffer = &anb->handle; + *stride = anb->stride; } return rc; } @@ -576,6 +571,14 @@ private: (type *) ((char *) __mptr - (char *)(&((type *)0)->member)); }) #endif + static int __lock_buffer(struct preview_stream_ops* w, + buffer_handle_t* buffer) + { + ANativeWindow *a = anw(w); + return a->lockBuffer(a, + container_of(buffer, ANativeWindowBuffer, handle)); + } + static int __enqueue_buffer(struct preview_stream_ops* w, buffer_handle_t* buffer) { @@ -641,6 +644,7 @@ private: void initHalPreviewWindow() { mHalPreviewWindow.nw.cancel_buffer = __cancel_buffer; + mHalPreviewWindow.nw.lock_buffer = __lock_buffer; mHalPreviewWindow.nw.dequeue_buffer = __dequeue_buffer; mHalPreviewWindow.nw.enqueue_buffer = __enqueue_buffer; mHalPreviewWindow.nw.set_buffer_count = __set_buffer_count; diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp index a011ae2fe357..9b09983e3a82 100644 --- a/services/camera/libcameraservice/CameraService.cpp +++ b/services/camera/libcameraservice/CameraService.cpp @@ -420,6 +420,10 @@ status_t CameraService::Client::unlock() { // allow anyone to use camera (after they lock the camera) status_t result = checkPid(); if (result == NO_ERROR) { + if (mHardware->recordingEnabled()) { + LOGE("Not allowed to unlock camera during recording."); + return INVALID_OPERATION; + } mClientPid = 0; LOG1("clear mCameraClient (pid %d)", callingPid); // we need to remove the reference to ICameraClient so that when the app @@ -756,6 +760,11 @@ status_t CameraService::Client::takePicture(int msgType) { status_t result = checkPidAndHardware(); if (result != NO_ERROR) return result; + if (mHardware->recordingEnabled()) { + LOGE("Cannot take picture during recording."); + return INVALID_OPERATION; + } + if ((msgType & CAMERA_MSG_RAW_IMAGE) && (msgType & CAMERA_MSG_RAW_IMAGE_NOTIFY)) { LOGE("CAMERA_MSG_RAW_IMAGE and CAMERA_MSG_RAW_IMAGE_NOTIFY" diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index e6f443a54bcd..8fb6274a7afb 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -131,8 +131,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { */ private List mNetRequestersPids[]; - private WifiWatchdogService mWifiWatchdogService; - // priority order of the nettrackers // (excluding dynamically set mNetworkPreference) // TODO - move mNetworkTypePreference into this @@ -278,6 +276,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { } RadioAttributes[] mRadioAttributes; + // the set of network types that can only be enabled by system/sig apps + List mProtectedNetworks; + public ConnectivityService( Context context, INetworkManagementService netd, INetworkPolicyManager policyManager) { if (DBG) log("ConnectivityService starting up"); @@ -381,6 +382,17 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } + mProtectedNetworks = new ArrayList<Integer>(); + int[] protectedNetworks = context.getResources().getIntArray( + com.android.internal.R.array.config_protectedNetworks); + for (int p : protectedNetworks) { + if ((mNetConfigs[p] != null) && (mProtectedNetworks.contains(p) == false)) { + mProtectedNetworks.add(p); + } else { + if (DBG) loge("Ignoring protectedNetwork " + p); + } + } + // high priority first mPriorityList = new int[mNetworksDefined]; { @@ -432,10 +444,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { wifiService.checkAndStartWifi(); mNetTrackers[ConnectivityManager.TYPE_WIFI] = wst; wst.startMonitoring(context, mHandler); - - //TODO: as part of WWS refactor, create only when needed - mWifiWatchdogService = new WifiWatchdogService(context); - break; case ConnectivityManager.TYPE_MOBILE: mNetTrackers[netType] = new MobileDataStateTracker(netType, @@ -802,6 +810,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { usedNetworkType = networkType; } } + + if (mProtectedNetworks.contains(usedNetworkType)) { + enforceConnectivityInternalPermission(); + } + NetworkStateTracker network = mNetTrackers[usedNetworkType]; if (network != null) { Integer currentPid = new Integer(getCallingPid()); @@ -1012,6 +1025,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { */ public boolean requestRouteToHostAddress(int networkType, byte[] hostAddress) { enforceChangePermission(); + if (mProtectedNetworks.contains(networkType)) { + enforceConnectivityInternalPermission(); + } + if (!ConnectivityManager.isNetworkTypeValid(networkType)) { return false; } @@ -1129,7 +1146,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { } public void setDataDependency(int networkType, boolean met) { - enforceChangePermission(); + enforceConnectivityInternalPermission(); + if (DBG) { log("setDataDependency(" + networkType + ", " + met + ")"); } @@ -1592,6 +1610,16 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } } + + // TODO: Temporary notifying upstread change to Tethering. + // @see bug/4455071 + /** Notify TetheringService if interface name has been changed. */ + if (TextUtils.equals(mNetTrackers[netType].getNetworkInfo().getReason(), + Phone.REASON_LINK_PROPERTIES_CHANGED)) { + if (isTetheringSupported()) { + mTethering.handleTetherIfaceChange(); + } + } } private void addPrivateDnsRoutes(NetworkStateTracker nt) { @@ -2017,7 +2045,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { break; case NetworkStateTracker.EVENT_CONFIGURATION_CHANGED: info = (NetworkInfo) msg.obj; - handleConnectivityChange(info.getType(), true); + // TODO: Temporary allowing network configuration + // change not resetting sockets. + // @see bug/4455071 + handleConnectivityChange(info.getType(), false); break; case EVENT_CLEAR_NET_TRANSITION_WAKELOCK: String causedBy = null; diff --git a/services/java/com/android/server/DnsPinger.java b/services/java/com/android/server/DnsPinger.java new file mode 100644 index 000000000000..5cfda7ec9965 --- /dev/null +++ b/services/java/com/android/server/DnsPinger.java @@ -0,0 +1,188 @@ +/* + * 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.server; + +import android.content.ContentResolver; +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.LinkProperties; +import android.os.SystemClock; +import android.util.Slog; + +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.SocketTimeoutException; +import java.util.Collection; +import java.util.Random; + +/** + * Performs a simple DNS "ping" by sending a "server status" query packet to the + * DNS server. As long as the server replies, we consider it a success. + * <p> + * We do not use a simple hostname lookup because that could be cached and the + * API may not differentiate between a time out and a failure lookup (which we + * really care about). + * <p> + * TODO : More general API. Wifi is currently hard coded + * TODO : Choice of DNS query location - current looks up www.android.com + * + * @hide + */ +public final class DnsPinger { + private static final boolean V = true; + + /** Number of bytes for the query */ + private static final int DNS_QUERY_BASE_SIZE = 33; + + /** The DNS port */ + private static final int DNS_PORT = 53; + + /** Used to generate IDs */ + private static Random sRandom = new Random(); + + private ConnectivityManager mConnectivityManager = null; + private ContentResolver mContentResolver; + private Context mContext; + + private String TAG; + + public DnsPinger(String TAG, Context context) { + mContext = context; + mContentResolver = context.getContentResolver(); + this.TAG = TAG; + } + + /** + * Gets the first DNS of the current Wifi AP. + * @return The first DNS of the current AP. + */ + public InetAddress getDns() { + if (mConnectivityManager == null) { + mConnectivityManager = (ConnectivityManager) mContext.getSystemService( + Context.CONNECTIVITY_SERVICE); + } + + LinkProperties linkProperties = mConnectivityManager.getLinkProperties( + ConnectivityManager.TYPE_WIFI); + if (linkProperties == null) + return null; + + Collection<InetAddress> dnses = linkProperties.getDnses(); + if (dnses == null || dnses.size() == 0) + return null; + + return dnses.iterator().next(); + } + + /** + * @return time to response. Negative value on error. + */ + public long pingDns(InetAddress dnsAddress, int timeout) { + DatagramSocket socket = null; + try { + socket = new DatagramSocket(); + + // Set some socket properties + socket.setSoTimeout(timeout); + + byte[] buf = new byte[DNS_QUERY_BASE_SIZE]; + fillQuery(buf); + + // Send the DNS query + + DatagramPacket packet = new DatagramPacket(buf, + buf.length, dnsAddress, DNS_PORT); + long start = SystemClock.elapsedRealtime(); + socket.send(packet); + + // Wait for reply (blocks for the above timeout) + DatagramPacket replyPacket = new DatagramPacket(buf, buf.length); + socket.receive(replyPacket); + + // If a timeout occurred, an exception would have been thrown. We + // got a reply! + return SystemClock.elapsedRealtime() - start; + + } catch (SocketTimeoutException e) { + // Squelch this exception. + return -1; + } catch (Exception e) { + if (V) { + Slog.v(TAG, "DnsPinger.pingDns got socket exception: ", e); + } + return -2; + } finally { + if (socket != null) { + socket.close(); + } + } + + } + + private static void fillQuery(byte[] buf) { + + /* + * See RFC2929 (though the bit tables in there are misleading for us. + * For example, the recursion desired bit is the 0th bit for us, but + * looking there it would appear as the 7th bit of the byte + */ + + // Make sure it's all zeroed out + for (int i = 0; i < buf.length; i++) + buf[i] = 0; + + // Form a query for www.android.com + + // [0-1] bytes are an ID, generate random ID for this query + buf[0] = (byte) sRandom.nextInt(256); + buf[1] = (byte) sRandom.nextInt(256); + + // [2-3] bytes are for flags. + buf[2] = 1; // Recursion desired + + // [4-5] bytes are for the query count + buf[5] = 1; // One query + + // [6-7] [8-9] [10-11] are all counts of other fields we don't use + + // [12-15] for www + writeString(buf, 12, "www"); + + // [16-23] for android + writeString(buf, 16, "android"); + + // [24-27] for com + writeString(buf, 24, "com"); + + // [29-30] bytes are for QTYPE, set to 1 + buf[30] = 1; + + // [31-32] bytes are for QCLASS, set to 1 + buf[32] = 1; + } + + private static void writeString(byte[] buf, int startPos, String string) { + int pos = startPos; + + // Write the length first + buf[pos++] = (byte) string.length(); + for (int i = 0; i < string.length(); i++) { + buf[pos++] = (byte) string.charAt(i); + } + } +} diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java index 2b01c5e158e2..e730d0d6311b 100644 --- a/services/java/com/android/server/NetworkManagementService.java +++ b/services/java/com/android/server/NetworkManagementService.java @@ -141,12 +141,26 @@ class NetworkManagementService extends INetworkManagementService.Stub { } /** - * Notify our observers of an interface link status change + * Notify our observers of an interface status change */ - private void notifyInterfaceLinkStatusChanged(String iface, boolean link) { + private void notifyInterfaceStatusChanged(String iface, boolean up) { for (INetworkManagementEventObserver obs : mObservers) { try { - obs.interfaceLinkStatusChanged(iface, link); + obs.interfaceStatusChanged(iface, up); + } catch (Exception ex) { + Slog.w(TAG, "Observer notifier failed", ex); + } + } + } + + /** + * Notify our observers of an interface link state change + * (typically, an Ethernet cable has been plugged-in or unplugged). + */ + private void notifyInterfaceLinkStateChanged(String iface, boolean up) { + for (INetworkManagementEventObserver obs : mObservers) { + try { + obs.interfaceLinkStateChanged(iface, up); } catch (Exception ex) { Slog.w(TAG, "Observer notifier failed", ex); } @@ -207,6 +221,7 @@ class NetworkManagementService extends INetworkManagementService.Stub { * Format: "NNN Iface added <name>" * "NNN Iface removed <name>" * "NNN Iface changed <name> <up/down>" + * "NNN Iface linkstatus <name> <up/down>" */ if (cooked.length < 4 || !cooked[1].equals("Iface")) { throw new IllegalStateException( @@ -219,7 +234,10 @@ class NetworkManagementService extends INetworkManagementService.Stub { notifyInterfaceRemoved(cooked[3]); return true; } else if (cooked[2].equals("changed") && cooked.length == 5) { - notifyInterfaceLinkStatusChanged(cooked[3], cooked[4].equals("up")); + notifyInterfaceStatusChanged(cooked[3], cooked[4].equals("up")); + return true; + } else if (cooked[2].equals("linkstate") && cooked.length == 5) { + notifyInterfaceLinkStateChanged(cooked[3], cooked[4].equals("up")); return true; } throw new IllegalStateException( diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index a23bacf72d15..cd68c6854153 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -209,9 +209,11 @@ class ServerThread extends Thread { bluetoothA2dp); bluetooth.initAfterA2dpRegistration(); + int airplaneModeOn = Settings.System.getInt(mContentResolver, + Settings.System.AIRPLANE_MODE_ON, 0); int bluetoothOn = Settings.Secure.getInt(mContentResolver, Settings.Secure.BLUETOOTH_ON, 0); - if (bluetoothOn > 0) { + if (airplaneModeOn == 0 && bluetoothOn != 0) { bluetooth.enable(); } } diff --git a/services/java/com/android/server/ThrottleService.java b/services/java/com/android/server/ThrottleService.java index 7266d7dd4f1d..d81dfdbc1b8d 100644 --- a/services/java/com/android/server/ThrottleService.java +++ b/services/java/com/android/server/ThrottleService.java @@ -179,14 +179,17 @@ public class ThrottleService extends IThrottleManager.Stub { mIface = iface; } - public void interfaceLinkStatusChanged(String iface, boolean link) { - if (link) { + public void interfaceStatusChanged(String iface, boolean up) { + if (up) { if (TextUtils.equals(iface, mIface)) { mHandler.obtainMessage(mMsg).sendToTarget(); } } } + public void interfaceLinkStateChanged(String iface, boolean up) { + } + public void interfaceAdded(String iface) { // TODO - an interface added in the UP state should also trigger a StatusChanged // notification.. diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java index cb55451f5494..77258910438b 100644 --- a/services/java/com/android/server/WifiService.java +++ b/services/java/com/android/server/WifiService.java @@ -342,6 +342,7 @@ public class WifiService extends IWifiManager.Stub { * Protected by mWifiStateTracker lock. */ private final WorkSource mTmpWorkSource = new WorkSource(); + private WifiWatchdogService mWifiWatchdogService; WifiService(Context context) { mContext = context; @@ -431,6 +432,9 @@ public class WifiService extends IWifiManager.Stub { Slog.i(TAG, "WifiService starting up with Wi-Fi " + (wifiEnabled ? "enabled" : "disabled")); setWifiEnabled(wifiEnabled); + + //TODO: as part of WWS refactor, create only when needed + mWifiWatchdogService = new WifiWatchdogService(mContext); } private boolean testAndClearWifiSavedState() { @@ -1155,6 +1159,10 @@ public class WifiService extends IWifiManager.Stub { pw.println(); pw.println("Locks held:"); mLocks.dump(pw); + + pw.println(); + pw.println("WifiWatchdogService dump"); + mWifiWatchdogService.dump(pw); } private class WifiLock extends DeathRecipient { diff --git a/services/java/com/android/server/WifiWatchdogService.java b/services/java/com/android/server/WifiWatchdogService.java index 56bfbe00fb09..0b7947882ee8 100644 --- a/services/java/com/android/server/WifiWatchdogService.java +++ b/services/java/com/android/server/WifiWatchdogService.java @@ -22,1429 +22,743 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.database.ContentObserver; -import android.net.ConnectivityManager; -import android.net.LinkProperties; import android.net.NetworkInfo; +import android.net.Uri; import android.net.wifi.ScanResult; +import android.net.wifi.SupplicantState; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; -import android.net.Uri; import android.os.Handler; +import android.os.HandlerThread; import android.os.Looper; import android.os.Message; +import android.os.SystemClock; import android.provider.Settings; import android.text.TextUtils; import android.util.Slog; import java.io.BufferedInputStream; -import java.io.InputStream; import java.io.IOException; -import java.net.DatagramPacket; -import java.net.DatagramSocket; +import java.io.InputStream; +import java.io.PrintWriter; import java.net.HttpURLConnection; -import java.net.InetAddress; -import java.net.SocketException; -import java.net.SocketTimeoutException; -import java.net.UnknownHostException; import java.net.URL; -import java.util.Collection; +import java.util.HashSet; import java.util.List; -import java.util.Random; import java.util.Scanner; /** * {@link WifiWatchdogService} monitors the initial connection to a Wi-Fi * network with multiple access points. After the framework successfully - * connects to an access point, the watchdog verifies whether the DNS server is - * reachable. If not, the watchdog blacklists the current access point, leading - * to a connection on another access point within the same network. + * connects to an access point, the watchdog verifies connectivity by 'pinging' + * the configured DNS server using {@link DnsPinger}. * <p> - * The watchdog has a few safeguards: - * <ul> - * <li>Only monitor networks with multiple access points - * <li>Only check at most {@link #getMaxApChecks()} different access points - * within the network before giving up + * On DNS check failure, the BSSID is blacklisted if it is reasonably likely + * that another AP might have internet access; otherwise the SSID is disabled. * <p> - * The watchdog checks for connectivity on an access point by ICMP pinging the - * DNS. There are settings that allow disabling the watchdog, or tweaking the - * acceptable packet loss (and other various parameters). - * <p> - * The core logic of the watchdog is done on the main watchdog thread. Wi-Fi - * callbacks can come in on other threads, so we must queue messages to the main - * watchdog thread's handler. Most (if not all) state is only written to from - * the main thread. + * On DNS success, the WatchdogService initiates a walled garden check via an + * http get. A browser windows is activated if a walled garden is detected. * - * {@hide} + * @hide */ public class WifiWatchdogService { - private static final String TAG = "WifiWatchdogService"; - private static final boolean V = false; - private static final boolean D = true; + + private static final String WWS_TAG = "WifiWatchdogService"; + + private static final boolean VDBG = true; + private static final boolean DBG = true; + + // Used for verbose logging + private String mDNSCheckLogStr; private Context mContext; private ContentResolver mContentResolver; private WifiManager mWifiManager; - private ConnectivityManager mConnectivityManager; - /** - * The main watchdog thread. - */ - private WifiWatchdogThread mThread; - /** - * The handler for the main watchdog thread. - */ private WifiWatchdogHandler mHandler; - private ContentObserver mContentObserver; + private DnsPinger mDnsPinger; - /** - * The current watchdog state. Only written from the main thread! - */ - private WatchdogState mState = WatchdogState.IDLE; - /** - * The SSID of the network that the watchdog is currently monitoring. Only - * touched in the main thread! - */ - private String mSsid; - /** - * The number of access points in the current network ({@link #mSsid}) that - * have been checked. Only touched in the main thread, using getter/setter methods. - */ - private int mBssidCheckCount; - /** Whether the current AP check should be canceled. */ - private boolean mShouldCancel; + private IntentFilter mIntentFilter; + private BroadcastReceiver mBroadcastReceiver; + private boolean mBroadcastsEnabled; - WifiWatchdogService(Context context) { - mContext = context; - mContentResolver = context.getContentResolver(); - mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); - createThread(); - - // The content observer to listen needs a handler, which createThread creates - registerForSettingsChanges(); - if (isWatchdogEnabled()) { - registerForWifiBroadcasts(); - } - - if (V) { - myLogV("WifiWatchdogService: Created"); - } - } + private static final int WIFI_SIGNAL_LEVELS = 4; /** - * Observes the watchdog on/off setting, and takes action when changed. + * Low signal is defined as less than or equal to cut off */ - private void registerForSettingsChanges() { - ContentResolver contentResolver = mContext.getContentResolver(); - contentResolver.registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_ON), false, - mContentObserver = new ContentObserver(mHandler) { - @Override - public void onChange(boolean selfChange) { - if (isWatchdogEnabled()) { - registerForWifiBroadcasts(); - } else { - unregisterForWifiBroadcasts(); - if (mHandler != null) { - mHandler.disableWatchdog(); - } - } - } - }); - } + private static final int LOW_SIGNAL_CUTOFF = 0; - /** - * @see android.provider.Settings.Secure#WIFI_WATCHDOG_ON - */ - private boolean isWatchdogEnabled() { - return Settings.Secure.getInt(mContentResolver, Settings.Secure.WIFI_WATCHDOG_ON, 1) == 1; - } - - /** - * @see android.provider.Settings.Secure#WIFI_WATCHDOG_AP_COUNT - */ - private int getApCount() { - return Settings.Secure.getInt(mContentResolver, - Settings.Secure.WIFI_WATCHDOG_AP_COUNT, 2); - } - - /** - * @see android.provider.Settings.Secure#WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT - */ - private int getInitialIgnoredPingCount() { - return Settings.Secure.getInt(mContentResolver, - Settings.Secure.WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT , 2); - } + private static final long MIN_LOW_SIGNAL_CHECK_INTERVAL = 2 * 60 * 1000; + private static final long MIN_SINGLE_DNS_CHECK_INTERVAL = 10 * 60 * 1000; + private static final long MIN_WALLED_GARDEN_INTERVAL = 15 * 60 * 1000; - /** - * @see android.provider.Settings.Secure#WIFI_WATCHDOG_PING_COUNT - */ - private int getPingCount() { - return Settings.Secure.getInt(mContentResolver, - Settings.Secure.WIFI_WATCHDOG_PING_COUNT, 4); - } - - /** - * @see android.provider.Settings.Secure#WIFI_WATCHDOG_PING_TIMEOUT_MS - */ - private int getPingTimeoutMs() { - return Settings.Secure.getInt(mContentResolver, - Settings.Secure.WIFI_WATCHDOG_PING_TIMEOUT_MS, 500); - } - - /** - * @see android.provider.Settings.Secure#WIFI_WATCHDOG_PING_DELAY_MS - */ - private int getPingDelayMs() { - return Settings.Secure.getInt(mContentResolver, - Settings.Secure.WIFI_WATCHDOG_PING_DELAY_MS, 250); - } + private static final int MAX_CHECKS_PER_SSID = 7; + private static final int NUM_DNS_PINGS = 5; + private static double MIN_RESPONSE_RATE = 0.50; - /** - * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED - */ - private Boolean isWalledGardenTestEnabled() { - return Settings.Secure.getInt(mContentResolver, - Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED, 1) == 1; - } + // TODO : Adjust multiple DNS downward to 250 on repeated failure + // private static final int MULTI_DNS_PING_TIMEOUT_MS = 250; - /** - * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_URL - */ - private String getWalledGardenUrl() { - String url = Settings.Secure.getString(mContentResolver, - Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_URL); - if (TextUtils.isEmpty(url)) return "http://www.google.com/"; - return url; - } + private static final int DNS_PING_TIMEOUT_MS = 1000; + private static final long DNS_PING_INTERVAL = 250; - /** - * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_PATTERN - */ - private String getWalledGardenPattern() { - String pattern = Settings.Secure.getString(mContentResolver, - Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_PATTERN); - if (TextUtils.isEmpty(pattern)) return "<title>.*Google.*</title>"; - return pattern; - } - - /** - * @see android.provider.Settings.Secure#WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE - */ - private int getAcceptablePacketLossPercentage() { - return Settings.Secure.getInt(mContentResolver, - Settings.Secure.WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE, 25); - } - - /** - * @see android.provider.Settings.Secure#WIFI_WATCHDOG_MAX_AP_CHECKS - */ - private int getMaxApChecks() { - return Settings.Secure.getInt(mContentResolver, - Settings.Secure.WIFI_WATCHDOG_MAX_AP_CHECKS, 7); - } - - /** - * @see android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED - */ - private boolean isBackgroundCheckEnabled() { - return Settings.Secure.getInt(mContentResolver, - Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED, 1) == 1; - } - - /** - * @see android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS - */ - private int getBackgroundCheckDelayMs() { - return Settings.Secure.getInt(mContentResolver, - Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS, 60000); - } - - /** - * @see android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS - */ - private int getBackgroundCheckTimeoutMs() { - return Settings.Secure.getInt(mContentResolver, - Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS, 1000); - } - - /** - * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WATCH_LIST - * @return the comma-separated list of SSIDs - */ - private String getWatchList() { - return Settings.Secure.getString(mContentResolver, - Settings.Secure.WIFI_WATCHDOG_WATCH_LIST); - } - - /** - * Registers to receive the necessary Wi-Fi broadcasts. - */ - private void registerForWifiBroadcasts() { - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); - intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); - mContext.registerReceiver(mReceiver, intentFilter); - } + private static final long BLACKLIST_FOLLOWUP_INTERVAL = 15 * 1000; - /** - * Unregisters from receiving the Wi-Fi broadcasts. - */ - private void unregisterForWifiBroadcasts() { - mContext.unregisterReceiver(mReceiver); - } + private Status mStatus = new Status(); - /** - * Creates the main watchdog thread, including waiting for the handler to be - * created. - */ - private void createThread() { - mThread = new WifiWatchdogThread(); - mThread.start(); - waitForHandlerCreation(); - } + private static class Status { + String bssid = ""; + String ssid = ""; - /** - * Unregister broadcasts and quit the watchdog thread - */ - //TODO: Change back to running WWS when needed -// private void quit() { -// unregisterForWifiBroadcasts(); -// mContext.getContentResolver().unregisterContentObserver(mContentObserver); -// mHandler.removeAllActions(); -// mHandler.getLooper().quit(); -// } + HashSet<String> allBssids = new HashSet<String>(); + int numFullDNSchecks = 0; - /** - * Waits for the main watchdog thread to create the handler. - */ - private void waitForHandlerCreation() { - synchronized(this) { - while (mHandler == null) { - try { - // Wait for the handler to be set by the other thread - wait(); - } catch (InterruptedException e) { - Slog.e(TAG, "Interrupted while waiting on handler."); - } - } - } - } + long lastSingleCheckTime = -24 * 60 * 60 * 1000; + long lastWalledGardenCheckTime = -24 * 60 * 60 * 1000; - // Utility methods - - /** - * Logs with the current thread. - */ - private static void myLogV(String message) { - Slog.v(TAG, "(" + Thread.currentThread().getName() + ") " + message); - } - - private static void myLogD(String message) { - Slog.d(TAG, "(" + Thread.currentThread().getName() + ") " + message); - } - - /** - * Gets the first DNS of the current AP. - * - * @return The first DNS of the current AP. - */ - private InetAddress getDns() { - if (mConnectivityManager == null) { - mConnectivityManager = (ConnectivityManager)mContext.getSystemService( - Context.CONNECTIVITY_SERVICE); - } + WatchdogState state = WatchdogState.INACTIVE; - LinkProperties linkProperties = mConnectivityManager.getLinkProperties( - ConnectivityManager.TYPE_WIFI); - if (linkProperties == null) return null; + // Info for dns check + int dnsCheckTries = 0; + int dnsCheckSuccesses = 0; - Collection<InetAddress> dnses = linkProperties.getDnses(); - if (dnses == null || dnses.size() == 0) return null; + public int signal = -200; - return dnses.iterator().next(); } - /** - * Checks whether the DNS can be reached using multiple attempts according - * to the current setting values. - * - * @return Whether the DNS is reachable - */ - private boolean checkDnsConnectivity() { - InetAddress dns = getDns(); - if (dns == null) { - if (V) { - myLogV("checkDnsConnectivity: Invalid DNS, returning false"); - } - return false; - } - - if (V) { - myLogV("checkDnsConnectivity: Checking " + dns.getHostAddress() + " for connectivity"); - } - - int numInitialIgnoredPings = getInitialIgnoredPingCount(); - int numPings = getPingCount(); - int pingDelay = getPingDelayMs(); - int acceptableLoss = getAcceptablePacketLossPercentage(); - - /** See {@link Secure#WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT} */ - int ignoredPingCounter = 0; - int pingCounter = 0; - int successCounter = 0; + private enum WatchdogState { + /** + * Full DNS check in progress + */ + DNS_FULL_CHECK, - // No connectivity check needed - if (numPings == 0) { - return true; - } + /** + * Walled Garden detected, will pop up browser next round. + */ + WALLED_GARDEN_DETECTED, - // Do the initial pings that we ignore - for (; ignoredPingCounter < numInitialIgnoredPings; ignoredPingCounter++) { - if (shouldCancel()) return false; - - boolean dnsAlive = DnsPinger.isDnsReachable(dns, getPingTimeoutMs()); - if (dnsAlive) { - /* - * Successful "ignored" pings are *not* ignored (they count in the total number - * of pings), but failures are really ignored. - */ - - // TODO: This is confusing logic and should be rewitten - // Here, successful 'ignored' pings are interpreted as a success in the below loop - pingCounter++; - successCounter++; - } + /** + * DNS failed, will blacklist/disable AP next round + */ + DNS_CHECK_FAILURE, - if (V) { - Slog.v(TAG, (dnsAlive ? " +" : " Ignored: -")); - } + /** + * Online or displaying walled garden auth page + */ + CHECKS_COMPLETE, - if (shouldCancel()) return false; + /** + * Watchdog idle, network has been blacklisted or received disconnect + * msg + */ + INACTIVE, - try { - Thread.sleep(pingDelay); - } catch (InterruptedException e) { - Slog.w(TAG, "Interrupted while pausing between pings", e); - } - } + BLACKLISTED_AP + } - // Do the pings that we use to measure packet loss - for (; pingCounter < numPings; pingCounter++) { - if (shouldCancel()) return false; + WifiWatchdogService(Context context) { + mContext = context; + mContentResolver = context.getContentResolver(); + mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + mDnsPinger = new DnsPinger("WifiWatchdogServer.DnsPinger", context); - if (DnsPinger.isDnsReachable(dns, getPingTimeoutMs())) { - successCounter++; - if (V) { - Slog.v(TAG, " +"); - } - } else { - if (V) { - Slog.v(TAG, " -"); - } - } + HandlerThread handlerThread = new HandlerThread("WifiWatchdogServiceThread"); + handlerThread.start(); + mHandler = new WifiWatchdogHandler(handlerThread.getLooper()); - if (shouldCancel()) return false; + setupNetworkReceiver(); - try { - Thread.sleep(pingDelay); - } catch (InterruptedException e) { - Slog.w(TAG, "Interrupted while pausing between pings", e); - } - } + // The content observer to listen needs a handler, which createThread + // creates + registerForSettingsChanges(); - //TODO: Integer division might cause problems down the road... - int packetLossPercentage = 100 * (numPings - successCounter) / numPings; - if (D) { - Slog.d(TAG, packetLossPercentage - + "% packet loss (acceptable is " + acceptableLoss + "%)"); + // Start things off + if (isWatchdogEnabled()) { + mHandler.sendEmptyMessage(WifiWatchdogHandler.MESSAGE_CONTEXT_EVENT); } - - return !shouldCancel() && (packetLossPercentage <= acceptableLoss); } - private boolean backgroundCheckDnsConnectivity() { - InetAddress dns = getDns(); - - if (dns == null) { - if (V) { - myLogV("backgroundCheckDnsConnectivity: DNS is empty, returning false"); + /** + * + */ + private void setupNetworkReceiver() { + mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { + mHandler.sendMessage(mHandler.obtainMessage( + WifiWatchdogHandler.MESSAGE_NETWORK_EVENT, + intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO) + )); + } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) { + mHandler.sendEmptyMessage(WifiWatchdogHandler.RSSI_CHANGE_EVENT); + } else if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) { + mHandler.sendEmptyMessage(WifiWatchdogHandler.SCAN_RESULTS_AVAILABLE); + } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { + mHandler.sendMessage(mHandler.obtainMessage( + WifiWatchdogHandler.WIFI_STATE_CHANGE, + intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, 4))); + } } - return false; - } + }; - if (V) { - myLogV("backgroundCheckDnsConnectivity: Background checking " + - dns.getHostAddress() + " for connectivity"); - } - - return DnsPinger.isDnsReachable(dns, getBackgroundCheckTimeoutMs()); + mIntentFilter = new IntentFilter(); + mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); + mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); + mIntentFilter.addAction(WifiManager.RSSI_CHANGED_ACTION); + mIntentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); } /** - * Signals the current action to cancel. + * Observes the watchdog on/off setting, and takes action when changed. */ - private void cancelCurrentAction() { - mShouldCancel = true; + private void registerForSettingsChanges() { + ContentObserver contentObserver = new ContentObserver(mHandler) { + @Override + public void onChange(boolean selfChange) { + mHandler.sendEmptyMessage((WifiWatchdogHandler.MESSAGE_CONTEXT_EVENT)); + } + }; + + mContext.getContentResolver().registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_ON), + false, contentObserver); } - - /** - * Helper to check whether to cancel. - * - * @return Whether to cancel processing the action. - */ - private boolean shouldCancel() { - if (V && mShouldCancel) { - myLogV("shouldCancel: Cancelling"); + + private void handleNewConnection() { + WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); + String newSsid = wifiInfo.getSSID(); + String newBssid = wifiInfo.getBSSID(); + + if (VDBG) { + Slog.v(WWS_TAG, String.format("handleConnected:: old (%s, %s) ==> new (%s, %s)", + mStatus.ssid, mStatus.bssid, newSsid, newBssid)); } - - return mShouldCancel; - } - - // Wi-Fi initiated callbacks (could be executed in another thread) - /** - * Called when connected to an AP (this can be the next AP in line, or - * it can be a completely different network). - * - * @param ssid The SSID of the access point. - * @param bssid The BSSID of the access point. - */ - private void onConnected(String ssid, String bssid) { - if (V) { - myLogV("onConnected: SSID: " + ssid + ", BSSID: " + bssid); + if (TextUtils.isEmpty(newSsid) || TextUtils.isEmpty(newBssid)) { + return; } - /* - * The current action being processed by the main watchdog thread is now - * stale, so cancel it. - */ - cancelCurrentAction(); - - if ((mSsid == null) || !mSsid.equals(ssid)) { - /* - * This is a different network than what the main watchdog thread is - * processing, dispatch the network change message on the main thread. - */ - mHandler.dispatchNetworkChanged(ssid); + if (!TextUtils.equals(mStatus.ssid, newSsid)) { + mStatus = new Status(); + mStatus.ssid = newSsid; } - - if (requiresWatchdog(ssid, bssid)) { - if (D) { - myLogD(ssid + " (" + bssid + ") requires the watchdog"); - } - // This access point requires a watchdog, so queue the check on the main thread - mHandler.checkAp(new AccessPoint(ssid, bssid)); - - } else { - if (D) { - myLogD(ssid + " (" + bssid + ") does not require the watchdog"); - } + mStatus.bssid = newBssid; + mStatus.allBssids.add(newBssid); + mStatus.signal = WifiManager.calculateSignalLevel(wifiInfo.getRssi(), WIFI_SIGNAL_LEVELS); - // This access point does not require a watchdog, so queue idle on the main thread - mHandler.idle(); - } - if (isWalledGardenTestEnabled()) mHandler.checkWalledGarden(ssid); + initDnsFullCheck(); } - - /** - * Called when Wi-Fi is enabled. - */ - private void onEnabled() { - cancelCurrentAction(); - // Queue a hard-reset of the state on the main thread - mHandler.reset(); - } - - /** - * Called when disconnected (or some other event similar to being disconnected). - */ - private void onDisconnected() { - if (V) { - myLogV("onDisconnected"); + + public void updateRssi() { + WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); + if (!TextUtils.equals(mStatus.ssid, wifiInfo.getSSID()) || + !TextUtils.equals(mStatus.bssid, wifiInfo.getBSSID())) { + return; } - - /* - * Disconnected from an access point, the action being processed by the - * watchdog thread is now stale, so cancel it. - */ - cancelCurrentAction(); - // Dispatch the disconnected to the main watchdog thread - mHandler.dispatchDisconnected(); - // Queue the action to go idle - mHandler.idle(); + + mStatus.signal = WifiManager.calculateSignalLevel(wifiInfo.getRssi(), WIFI_SIGNAL_LEVELS); } /** - * Checks whether an access point requires watchdog monitoring. - * - * @param ssid The SSID of the access point. - * @param bssid The BSSID of the access point. - * @return Whether the access point/network should be monitored by the - * watchdog. + * Single step in state machine */ - private boolean requiresWatchdog(String ssid, String bssid) { - if (V) { - myLogV("requiresWatchdog: SSID: " + ssid + ", BSSID: " + bssid); - } - - WifiInfo info = null; - if (ssid == null) { - /* - * This is called from a Wi-Fi callback, so assume the WifiInfo does - * not have stale data. - */ - info = mWifiManager.getConnectionInfo(); - ssid = info.getSSID(); - if (ssid == null) { - // It's still null, give up - if (V) { - Slog.v(TAG, " Invalid SSID, returning false"); - } - return false; - } - } - - if (TextUtils.isEmpty(bssid)) { - // Similar as above - if (info == null) { - info = mWifiManager.getConnectionInfo(); - } - bssid = info.getBSSID(); - if (TextUtils.isEmpty(bssid)) { - // It's still null, give up - if (V) { - Slog.v(TAG, " Invalid BSSID, returning false"); - } - return false; - } - } - - if (!isOnWatchList(ssid)) { - if (V) { - Slog.v(TAG, " SSID not on watch list, returning false"); - } - return false; - } + private void handleStateStep() { + // Slog.v(WWS_TAG, "handleStateStep:: " + mStatus.state); - // The watchdog only monitors networks with multiple APs - if (!hasRequiredNumberOfAps(ssid)) { - return false; - } - - return true; - } + switch (mStatus.state) { + case DNS_FULL_CHECK: + if (VDBG) { + Slog.v(WWS_TAG, "DNS_FULL_CHECK: " + mDNSCheckLogStr); + } - private boolean isOnWatchList(String ssid) { - String watchList; + long pingResponseTime = mDnsPinger.pingDns(mDnsPinger.getDns(), + DNS_PING_TIMEOUT_MS); - if (ssid == null || (watchList = getWatchList()) == null) { - return false; - } + mStatus.dnsCheckTries++; + if (pingResponseTime >= 0) + mStatus.dnsCheckSuccesses++; - String[] list = watchList.split(" *, *"); + if (DBG) { + if (pingResponseTime >= 0) { + mDNSCheckLogStr += " | " + pingResponseTime; + } else { + mDNSCheckLogStr += " | " + "x"; + } + } - for (String name : list) { - if (ssid.equals(name)) { - return true; - } - } + switch (currentDnsCheckStatus()) { + case SUCCESS: + if (DBG) { + Slog.d(WWS_TAG, mDNSCheckLogStr + " -- Success"); + } + doWalledGardenCheck(); + break; + case FAILURE: + if (DBG) { + Slog.d(WWS_TAG, mDNSCheckLogStr + " -- Failure"); + } + mStatus.state = WatchdogState.DNS_CHECK_FAILURE; + break; + case INCOMPLETE: + // Taking no action + break; + } + break; + case DNS_CHECK_FAILURE: + WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); + if (!mStatus.ssid.equals(wifiInfo.getSSID()) || + !mStatus.bssid.equals(wifiInfo.getBSSID())) { + Slog.i(WWS_TAG, "handleState DNS_CHECK_FAILURE:: network has changed!"); + mStatus.state = WatchdogState.INACTIVE; + break; + } - return false; - } - - /** - * Checks if the current scan results have multiple access points with an SSID. - * - * @param ssid The SSID to check. - * @return Whether the SSID has multiple access points. - */ - private boolean hasRequiredNumberOfAps(String ssid) { - List<ScanResult> results = mWifiManager.getScanResults(); - if (results == null) { - if (V) { - myLogV("hasRequiredNumberOfAps: Got null scan results, returning false"); - } - return false; - } - - int numApsRequired = getApCount(); - int numApsFound = 0; - int resultsSize = results.size(); - for (int i = 0; i < resultsSize; i++) { - ScanResult result = results.get(i); - if (result == null) continue; - if (result.SSID == null) continue; - - if (result.SSID.equals(ssid)) { - numApsFound++; - - if (numApsFound >= numApsRequired) { - if (V) { - myLogV("hasRequiredNumberOfAps: SSID: " + ssid + ", returning true"); + if (mStatus.numFullDNSchecks >= mStatus.allBssids.size() || + mStatus.numFullDNSchecks >= MAX_CHECKS_PER_SSID) { + disableAP(wifiInfo); + } else { + blacklistAP(); + } + break; + case WALLED_GARDEN_DETECTED: + popUpBrowser(); + mStatus.state = WatchdogState.CHECKS_COMPLETE; + break; + case BLACKLISTED_AP: + WifiInfo wifiInfo2 = mWifiManager.getConnectionInfo(); + if (wifiInfo2.getSupplicantState() != SupplicantState.COMPLETED) { + Slog.d(WWS_TAG, + "handleState::BlacklistedAP - offline, but didn't get disconnect!"); + mStatus.state = WatchdogState.INACTIVE; + break; + } + if (mStatus.bssid.equals(wifiInfo2.getBSSID())) { + Slog.d(WWS_TAG, "handleState::BlacklistedAP - connected to same bssid"); + if (!handleSingleDnsCheck()) { + disableAP(wifiInfo2); + break; } - return true; } - } - } - - if (V) { - myLogV("hasRequiredNumberOfAps: SSID: " + ssid + ", returning false"); + + Slog.d(WWS_TAG, "handleState::BlacklistedAP - Simiulating a new connection"); + handleNewConnection(); + break; } - return false; - } - - // Watchdog logic (assume all of these methods will be in our main thread) - - /** - * Handles a Wi-Fi network change (for example, from networkA to networkB). - */ - private void handleNetworkChanged(String ssid) { - // Set the SSID being monitored to the new SSID - mSsid = ssid; - // Set various state to that when being idle - setIdleState(true); } - - /** - * Handles checking whether an AP is a "good" AP. If not, it will be blacklisted. - * - * @param ap The access point to check. - */ - private void handleCheckAp(AccessPoint ap) { - // Reset the cancel state since this is the entry point of this action - mShouldCancel = false; - - if (V) { - myLogV("handleCheckAp: AccessPoint: " + ap); - } - - // Make sure we are not sleeping - if (mState == WatchdogState.SLEEP) { - if (V) { - Slog.v(TAG, " Sleeping (in " + mSsid + "), so returning"); - } + + private void doWalledGardenCheck() { + if (!isWalledGardenTestEnabled()) { + if (VDBG) + Slog.v(WWS_TAG, "Skipping walled garden check - disabled"); + mStatus.state = WatchdogState.CHECKS_COMPLETE; return; } - - mState = WatchdogState.CHECKING_AP; - - /* - * Checks to make sure we haven't exceeded the max number of checks - * we're allowed per network - */ - incrementBssidCheckCount(); - if (getBssidCheckCount() > getMaxApChecks()) { - if (V) { - Slog.v(TAG, " Passed the max attempts (" + getMaxApChecks() - + "), going to sleep for " + mSsid); + long waitTime = waitTime(MIN_WALLED_GARDEN_INTERVAL, + mStatus.lastWalledGardenCheckTime); + if (waitTime > 0) { + if (VDBG) { + Slog.v(WWS_TAG, "Skipping walled garden check - wait " + + waitTime + " ms."); } - mHandler.sleep(mSsid); + mStatus.state = WatchdogState.CHECKS_COMPLETE; return; } - // Do the check - boolean isApAlive = checkDnsConnectivity(); - - if (V) { - Slog.v(TAG, " Is it alive: " + isApAlive); + mStatus.lastWalledGardenCheckTime = SystemClock.elapsedRealtime(); + if (isWalledGardenConnection()) { + if (DBG) + Slog.d(WWS_TAG, + "Walled garden test complete - walled garden detected"); + mStatus.state = WatchdogState.WALLED_GARDEN_DETECTED; + } else { + if (DBG) + Slog.d(WWS_TAG, "Walled garden test complete - online"); + mStatus.state = WatchdogState.CHECKS_COMPLETE; } + } - // Take action based on results - if (isApAlive) { - handleApAlive(ap); - } else { - handleApUnresponsive(ap); + private boolean handleSingleDnsCheck() { + mStatus.lastSingleCheckTime = SystemClock.elapsedRealtime(); + long responseTime = mDnsPinger.pingDns(mDnsPinger.getDns(), + DNS_PING_TIMEOUT_MS); + if (DBG) { + Slog.d(WWS_TAG, "Ran a single DNS ping. Response time: " + responseTime); } + if (responseTime < 0) { + return false; + } + return true; + } /** - * Handles the case when an access point is alive. - * - * @param ap The access point. + * @return Delay in MS before next single DNS check can proceed. */ - private void handleApAlive(AccessPoint ap) { - // Check whether we are stale and should cancel - if (shouldCancel()) return; - // We're satisfied with this AP, so go idle - setIdleState(false); - - if (D) { - myLogD("AP is alive: " + ap.toString()); + private long timeToNextScheduledDNSCheck() { + if (mStatus.signal > LOW_SIGNAL_CUTOFF) { + return waitTime(MIN_SINGLE_DNS_CHECK_INTERVAL, mStatus.lastSingleCheckTime); + } else { + return waitTime(MIN_LOW_SIGNAL_CHECK_INTERVAL, mStatus.lastSingleCheckTime); } - - // Queue the next action to be a background check - mHandler.backgroundCheckAp(ap); } - + /** - * Handles an unresponsive AP by blacklisting it. + * Helper to return wait time left given a min interval and last run * - * @param ap The access point. + * @param interval minimum wait interval + * @param lastTime last time action was performed in + * SystemClock.elapsedRealtime() + * @return non negative time to wait */ - private void handleApUnresponsive(AccessPoint ap) { - // Check whether we are stale and should cancel - if (shouldCancel()) return; - // This AP is "bad", switch to another - mState = WatchdogState.SWITCHING_AP; - - if (D) { - myLogD("AP is dead: " + ap.toString()); - } - - // Black list this "bad" AP, this will cause an attempt to connect to another - blacklistAp(ap.bssid); - // Initiate an association to an alternate AP - mWifiManager.reassociate(); + private static long waitTime(long interval, long lastTime) { + long wait = interval + lastTime - SystemClock.elapsedRealtime(); + return wait > 0 ? wait : 0; } - private void blacklistAp(String bssid) { - if (TextUtils.isEmpty(bssid)) { - return; - } - - // Before taking action, make sure we should not cancel our processing - if (shouldCancel()) return; - - mWifiManager.addToBlacklist(bssid); - - if (D) { - myLogD("Blacklisting " + bssid); - } + private void popUpBrowser() { + Uri uri = Uri.parse("http://www.google.com"); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | + Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(intent); + } + + private void disableAP(WifiInfo info) { + // TODO : Unban networks if they had low signal ? + Slog.i(WWS_TAG, String.format("Disabling current SSID, %s [bssid %s]. " + + "numChecks %d, numAPs %d", mStatus.ssid, mStatus.bssid, + mStatus.numFullDNSchecks, mStatus.allBssids.size())); + mWifiManager.disableNetwork(info.getNetworkId()); + mStatus.state = WatchdogState.INACTIVE; + } + + private void blacklistAP() { + Slog.i(WWS_TAG, String.format("Blacklisting current BSSID %s [ssid %s]. " + + "numChecks %d, numAPs %d", mStatus.bssid, mStatus.ssid, + mStatus.numFullDNSchecks, mStatus.allBssids.size())); + + mWifiManager.addToBlacklist(mStatus.bssid); + mWifiManager.reassociate(); + mStatus.state = WatchdogState.BLACKLISTED_AP; } /** - * Handles a single background check. If it fails, it should trigger a - * normal check. If it succeeds, it should queue another background check. - * - * @param ap The access point to do a background check for. If this is no - * longer the current AP, it is okay to return without any - * processing. + * Checks the scan for new BBIDs using current mSsid */ - private void handleBackgroundCheckAp(AccessPoint ap) { - // Reset the cancel state since this is the entry point of this action - mShouldCancel = false; - - if (V) { - myLogV("handleBackgroundCheckAp: AccessPoint: " + ap); - } - - // Make sure we are not sleeping - if (mState == WatchdogState.SLEEP) { - if (V) { - Slog.v(TAG, " handleBackgroundCheckAp: Sleeping (in " + mSsid + "), so returning"); - } - return; - } - - // Make sure the AP we're supposed to be background checking is still the active one - WifiInfo info = mWifiManager.getConnectionInfo(); - if (info.getSSID() == null || !info.getSSID().equals(ap.ssid)) { - if (V) { - myLogV("handleBackgroundCheckAp: We are no longer connected to " - + ap + ", and instead are on " + info); - } - return; - } - - if (info.getBSSID() == null || !info.getBSSID().equals(ap.bssid)) { - if (V) { - myLogV("handleBackgroundCheckAp: We are no longer connected to " - + ap + ", and instead are on " + info); + private void updateBssids() { + String curSsid = mStatus.ssid; + HashSet<String> bssids = mStatus.allBssids; + List<ScanResult> results = mWifiManager.getScanResults(); + int oldNumBssids = bssids.size(); + + if (results == null) { + if (VDBG) { + Slog.v(WWS_TAG, "updateBssids: Got null scan results!"); } return; } - // Do the check - boolean isApAlive = backgroundCheckDnsConnectivity(); - - if (V && !isApAlive) { - Slog.v(TAG, " handleBackgroundCheckAp: Is it alive: " + isApAlive); + for (ScanResult result : results) { + if (result != null && curSsid.equals(result.SSID)) + bssids.add(result.BSSID); } - if (shouldCancel()) { - return; - } - - // Take action based on results - if (isApAlive) { - // Queue another background check - mHandler.backgroundCheckAp(ap); - - } else { - if (D) { - myLogD("Background check failed for " + ap.toString()); - } - - // Queue a normal check, so it can take proper action - mHandler.checkAp(ap); - } + // if (VDBG && bssids.size() - oldNumBssids > 0) { + // Slog.v(WWS_TAG, + // String.format("updateBssids:: Found %d new APs (total %d) on SSID %s", + // bssids.size() - oldNumBssids, bssids.size(), curSsid)); + // } } - - /** - * Handles going to sleep for this network. Going to sleep means we will not - * monitor this network anymore. - * - * @param ssid The network that will not be monitored anymore. - */ - private void handleSleep(String ssid) { - // Make sure the network we're trying to sleep in is still the current network - if (ssid != null && ssid.equals(mSsid)) { - mState = WatchdogState.SLEEP; - if (D) { - myLogD("Going to sleep for " + ssid); - } - - /* - * Before deciding to go to sleep, we may have checked a few APs - * (and blacklisted them). Clear the blacklist so the AP with best - * signal is chosen. - */ - mWifiManager.clearBlacklist(); - - if (V) { - myLogV("handleSleep: Set state to SLEEP and cleared blacklist"); - } - } + enum DnsCheckStatus { + SUCCESS, + FAILURE, + INCOMPLETE } /** - * Handles an access point disconnection. + * Computes the current results of the dns check, ends early if outcome is + * assured. */ - private void handleDisconnected() { - /* - * We purposefully do not change mSsid to null. This is to handle - * disconnected followed by connected better (even if there is some - * duration in between). For example, if the watchdog went to sleep in a - * network, and then the phone goes to sleep, when the phone wakes up we - * still want to be in the sleeping state. When the phone went to sleep, - * we would have gotten a disconnected event which would then set mSsid - * = null. This is bad, since the following connect would cause us to do - * the "network is good?" check all over again. */ - - /* - * Set the state as if we were idle (don't come out of sleep, only - * hard reset and network changed should do that. + private DnsCheckStatus currentDnsCheckStatus() { + /** + * After a full ping count, if we have more responses than this cutoff, + * the outcome is success; else it is 'failure'. */ - setIdleState(false); - } + double pingResponseCutoff = MIN_RESPONSE_RATE * NUM_DNS_PINGS; + int remainingChecks = NUM_DNS_PINGS - mStatus.dnsCheckTries; - /** - * Handles going idle. Idle means we are satisfied with the current state of - * things, but if a new connection occurs we'll re-evaluate. - */ - private void handleIdle() { - // Reset the cancel state since this is the entry point for this action - mShouldCancel = false; - - if (V) { - myLogV("handleSwitchToIdle"); - } - - // If we're sleeping, don't do anything - if (mState == WatchdogState.SLEEP) { - Slog.v(TAG, " Sleeping (in " + mSsid + "), so returning"); - return; - } - - // Set the idle state - setIdleState(false); - - if (V) { - Slog.v(TAG, " Set state to IDLE"); - } - } - - /** - * Sets the state as if we are going idle. - */ - private void setIdleState(boolean forceIdleState) { - // Setting idle state does not kick us out of sleep unless the forceIdleState is set - if (forceIdleState || (mState != WatchdogState.SLEEP)) { - mState = WatchdogState.IDLE; + /** + * Our final success count will be at least this big, so we're + * guaranteed to succeed. + */ + if (mStatus.dnsCheckSuccesses >= pingResponseCutoff) { + return DnsCheckStatus.SUCCESS; } - resetBssidCheckCount(); - } - - /** - * Handles a hard reset. A hard reset is rarely used, but when used it - * should revert anything done by the watchdog monitoring. - */ - private void handleReset() { - mWifiManager.clearBlacklist(); - setIdleState(true); - } - - // Inner classes - /** - * Possible states for the watchdog to be in. - */ - private static enum WatchdogState { - /** The watchdog is currently idle, but it is still responsive to future AP checks in this network. */ - IDLE, - /** The watchdog is sleeping, so it will not try any AP checks for the network. */ - SLEEP, - /** The watchdog is currently checking an AP for connectivity. */ - CHECKING_AP, - /** The watchdog is switching to another AP in the network. */ - SWITCHING_AP - } + /** + * Our final count will be at most the current count plus the remaining + * pings - we're guaranteed to fail. + */ + if (remainingChecks + mStatus.dnsCheckSuccesses < pingResponseCutoff) { + return DnsCheckStatus.FAILURE; + } - private int getBssidCheckCount() { - return mBssidCheckCount; + return DnsCheckStatus.INCOMPLETE; } - private void incrementBssidCheckCount() { - mBssidCheckCount++; - } + private void initDnsFullCheck() { + if (DBG) { + Slog.d(WWS_TAG, "Starting DNS pings at " + SystemClock.elapsedRealtime()); + } + mStatus.numFullDNSchecks++; + mStatus.dnsCheckSuccesses = 0; + mStatus.dnsCheckTries = 0; + mStatus.state = WatchdogState.DNS_FULL_CHECK; - private void resetBssidCheckCount() { - this.mBssidCheckCount = 0; + if (DBG) { + mDNSCheckLogStr = String.format("Dns Check %d. Pinging %s on ssid [%s]: ", + mStatus.numFullDNSchecks, mDnsPinger.getDns().getHostAddress(), + mStatus.ssid); + } } /** - * The main thread for the watchdog monitoring. This will be turned into a - * {@link Looper} thread. + * DNS based detection techniques do not work at all hotspots. The one sure + * way to check a walled garden is to see if a URL fetch on a known address + * fetches the data we expect */ - private class WifiWatchdogThread extends Thread { - WifiWatchdogThread() { - super("WifiWatchdogThread"); - } - - @Override - public void run() { - // Set this thread up so the handler will work on it - Looper.prepare(); - - synchronized(WifiWatchdogService.this) { - mHandler = new WifiWatchdogHandler(); - - // Notify that the handler has been created - WifiWatchdogService.this.notify(); + private boolean isWalledGardenConnection() { + InputStream in = null; + HttpURLConnection urlConnection = null; + try { + URL url = new URL(getWalledGardenUrl()); + urlConnection = (HttpURLConnection) url.openConnection(); + in = new BufferedInputStream(urlConnection.getInputStream()); + Scanner scanner = new Scanner(in); + if (scanner.findInLine(getWalledGardenPattern()) != null) { + return false; + } else { + return true; } - - // Listen for messages to the handler - Looper.loop(); + } catch (IOException e) { + return false; + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + } + } + if (urlConnection != null) + urlConnection.disconnect(); } } /** - * The main thread's handler. There are 'actions', and just general - * 'messages'. There should only ever be one 'action' in the queue (aside - * from the one being processed, if any). There may be multiple messages in - * the queue. So, actions are replaced by more recent actions, where as - * messages will be executed for sure. Messages end up being used to just - * change some state, and not really take any action. - * <p> * There is little logic inside this class, instead methods of the form * "handle___" are called in the main {@link WifiWatchdogService}. */ private class WifiWatchdogHandler extends Handler { - /** Check whether the AP is "good". The object will be an {@link AccessPoint}. */ - static final int ACTION_CHECK_AP = 1; - /** Go into the idle state. */ - static final int ACTION_IDLE = 2; /** - * Performs a periodic background check whether the AP is still "good". - * The object will be an {@link AccessPoint}. + * Major network event, object is NetworkInfo */ - static final int ACTION_BACKGROUND_CHECK_AP = 3; - /** Check whether the connection is a walled garden */ - static final int ACTION_CHECK_WALLED_GARDEN = 4; - + static final int MESSAGE_NETWORK_EVENT = 1; /** - * Go to sleep for the current network. We are conservative with making - * this a message rather than action. We want to make sure our main - * thread sees this message, but if it were an action it could be - * removed from the queue and replaced by another action. The main - * thread will ensure when it sees the message that the state is still - * valid for going to sleep. - * <p> - * For an explanation of sleep, see {@link android.provider.Settings.Secure#WIFI_WATCHDOG_MAX_AP_CHECKS}. + * Change in settings, no object */ - static final int MESSAGE_SLEEP = 101; - /** Disables the watchdog. */ - static final int MESSAGE_DISABLE_WATCHDOG = 102; - /** The network has changed. */ - static final int MESSAGE_NETWORK_CHANGED = 103; - /** The current access point has disconnected. */ - static final int MESSAGE_DISCONNECTED = 104; - /** Performs a hard-reset on the watchdog state. */ - static final int MESSAGE_RESET = 105; - - /* Walled garden detection */ - private String mLastSsid; - private long mLastTime; - private final long MIN_WALLED_GARDEN_TEST_INTERVAL = 15 * 60 * 1000; //15 minutes - - void checkWalledGarden(String ssid) { - sendMessage(obtainMessage(ACTION_CHECK_WALLED_GARDEN, ssid)); - } + static final int MESSAGE_CONTEXT_EVENT = 2; - void checkAp(AccessPoint ap) { - removeAllActions(); - sendMessage(obtainMessage(ACTION_CHECK_AP, ap)); - } - - void backgroundCheckAp(AccessPoint ap) { - if (!isBackgroundCheckEnabled()) return; - - removeAllActions(); - sendMessageDelayed(obtainMessage(ACTION_BACKGROUND_CHECK_AP, ap), - getBackgroundCheckDelayMs()); - } - - void idle() { - removeAllActions(); - sendMessage(obtainMessage(ACTION_IDLE)); - } - - void sleep(String ssid) { - removeAllActions(); - sendMessage(obtainMessage(MESSAGE_SLEEP, ssid)); - } - - void disableWatchdog() { - removeAllActions(); - sendMessage(obtainMessage(MESSAGE_DISABLE_WATCHDOG)); - } - - void dispatchNetworkChanged(String ssid) { - removeAllActions(); - sendMessage(obtainMessage(MESSAGE_NETWORK_CHANGED, ssid)); - } + /** + * Change in signal strength + */ + static final int RSSI_CHANGE_EVENT = 3; + static final int SCAN_RESULTS_AVAILABLE = 4; - void dispatchDisconnected() { - removeAllActions(); - sendMessage(obtainMessage(MESSAGE_DISCONNECTED)); - } + static final int WIFI_STATE_CHANGE = 5; - void reset() { - removeAllActions(); - sendMessage(obtainMessage(MESSAGE_RESET)); - } - - private void removeAllActions() { - removeMessages(ACTION_CHECK_AP); - removeMessages(ACTION_IDLE); - removeMessages(ACTION_BACKGROUND_CHECK_AP); - } - - @Override - public void handleMessage(Message msg) { - if (V) { - myLogV("handleMessage: " + msg.what); - } - switch (msg.what) { - case MESSAGE_NETWORK_CHANGED: - handleNetworkChanged((String) msg.obj); - break; - case ACTION_CHECK_AP: - handleCheckAp((AccessPoint) msg.obj); - break; - case ACTION_BACKGROUND_CHECK_AP: - handleBackgroundCheckAp((AccessPoint) msg.obj); - break; - case ACTION_CHECK_WALLED_GARDEN: - handleWalledGardenCheck((String) msg.obj); - break; - case MESSAGE_SLEEP: - handleSleep((String) msg.obj); - break; - case ACTION_IDLE: - handleIdle(); - break; - case MESSAGE_DISABLE_WATCHDOG: - handleIdle(); - break; - case MESSAGE_DISCONNECTED: - handleDisconnected(); - break; - case MESSAGE_RESET: - handleReset(); - break; - } - } + /** + * Single step of state machine. One DNS check, or one WalledGarden + * check, or one external action. We separate out external actions to + * increase chance of detecting that a check failure is caused by change + * in network status. Messages should have an arg1 which to sync status + * messages. + */ + static final int CHECK_SEQUENCE_STEP = 10; + static final int SINGLE_DNS_CHECK = 11; /** - * DNS based detection techniques do not work at all hotspots. The one sure way to check - * a walled garden is to see if a URL fetch on a known address fetches the data we - * expect + * @param looper */ - private boolean isWalledGardenConnection() { - InputStream in = null; - HttpURLConnection urlConnection = null; - try { - URL url = new URL(getWalledGardenUrl()); - urlConnection = (HttpURLConnection) url.openConnection(); - in = new BufferedInputStream(urlConnection.getInputStream()); - Scanner scanner = new Scanner(in); - if (scanner.findInLine(getWalledGardenPattern()) != null) { - return false; - } else { - return true; - } - } catch (IOException e) { - return false; - } finally { - if (in != null) { - try { - in.close(); - } catch (IOException e) { - } - } - if (urlConnection != null) urlConnection.disconnect(); - } + public WifiWatchdogHandler(Looper looper) { + super(looper); } - private void handleWalledGardenCheck(String ssid) { - long currentTime = System.currentTimeMillis(); - //Avoid a walled garden test on the same network if one was already done - //within MIN_WALLED_GARDEN_TEST_INTERVAL. This will handle scenarios where - //there are frequent network disconnections - if (ssid.equals(mLastSsid) && - (currentTime - mLastTime) < MIN_WALLED_GARDEN_TEST_INTERVAL) { - return; - } - - mLastTime = currentTime; - mLastSsid = ssid; + boolean singleCheckQueued = false; + long queuedSingleDnsCheckArrival; - if (isWalledGardenConnection()) { - Uri uri = Uri.parse("http://www.google.com"); - Intent intent = new Intent(Intent.ACTION_VIEW, uri); - intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | - Intent.FLAG_ACTIVITY_NEW_TASK); - mContext.startActivity(intent); - } + /** + * Sends a singleDnsCheck message with shortest time - guards against + * multiple. + */ + private boolean queueSingleDnsCheck() { + long delay = timeToNextScheduledDNSCheck(); + long newArrival = delay + SystemClock.elapsedRealtime(); + if (singleCheckQueued && queuedSingleDnsCheckArrival <= newArrival) + return true; + queuedSingleDnsCheckArrival = newArrival; + singleCheckQueued = true; + removeMessages(SINGLE_DNS_CHECK); + return sendMessageDelayed(obtainMessage(SINGLE_DNS_CHECK), delay); } - } - /** - * Receives Wi-Fi broadcasts. - * <p> - * There is little logic in this class, instead methods of the form "on___" - * are called in the {@link WifiWatchdogService}. - */ - private BroadcastReceiver mReceiver = new BroadcastReceiver() { + boolean checkSequenceQueued = false; + long queuedCheckSequenceArrival; - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { - handleNetworkStateChanged( - (NetworkInfo) intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO)); - } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { - handleWifiStateChanged(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, - WifiManager.WIFI_STATE_UNKNOWN)); - } + /** + * Sends a state_machine_step message if the delay requested is lower + * than the current delay. + */ + private boolean sendCheckSequenceStep(long delay) { + long newArrival = delay + SystemClock.elapsedRealtime(); + if (checkSequenceQueued && queuedCheckSequenceArrival <= newArrival) + return true; + queuedCheckSequenceArrival = newArrival; + checkSequenceQueued = true; + removeMessages(CHECK_SEQUENCE_STEP); + return sendMessageDelayed(obtainMessage(CHECK_SEQUENCE_STEP), delay); } - private void handleNetworkStateChanged(NetworkInfo info) { - if (V) { - myLogV("Receiver.handleNetworkStateChanged: NetworkInfo: " - + info); - } - - switch (info.getState()) { - case CONNECTED: - WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); - if (wifiInfo.getSSID() == null || wifiInfo.getBSSID() == null) { - if (V) { - myLogV("handleNetworkStateChanged: Got connected event but SSID or BSSID are null. SSID: " - + wifiInfo.getSSID() - + ", BSSID: " - + wifiInfo.getBSSID() + ", ignoring event"); - } + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case CHECK_SEQUENCE_STEP: + checkSequenceQueued = false; + handleStateStep(); + if (mStatus.state == WatchdogState.CHECKS_COMPLETE) { + queueSingleDnsCheck(); + } else if (mStatus.state == WatchdogState.DNS_FULL_CHECK) { + sendCheckSequenceStep(DNS_PING_INTERVAL); + } else if (mStatus.state == WatchdogState.BLACKLISTED_AP) { + sendCheckSequenceStep(BLACKLIST_FOLLOWUP_INTERVAL); + } else if (mStatus.state != WatchdogState.INACTIVE) { + sendCheckSequenceStep(0); + } + return; + case MESSAGE_NETWORK_EVENT: + if (!mBroadcastsEnabled) { + Slog.e(WWS_TAG, + "MessageNetworkEvent - WatchdogService not enabled... returning"); return; } - onConnected(wifiInfo.getSSID(), wifiInfo.getBSSID()); - break; + NetworkInfo info = (NetworkInfo) msg.obj; + switch (info.getState()) { + case DISCONNECTED: + mStatus.state = WatchdogState.INACTIVE; + return; + case CONNECTED: + handleNewConnection(); + sendCheckSequenceStep(0); + } + return; + case SINGLE_DNS_CHECK: + singleCheckQueued = false; + if (mStatus.state != WatchdogState.CHECKS_COMPLETE) { + Slog.d(WWS_TAG, "Single check returning, curState: " + mStatus.state); + break; + } + + if (!handleSingleDnsCheck()) { + initDnsFullCheck(); + sendCheckSequenceStep(0); + } else { + queueSingleDnsCheck(); + } - case DISCONNECTED: - onDisconnected(); + break; + case RSSI_CHANGE_EVENT: + updateRssi(); + if (mStatus.state == WatchdogState.CHECKS_COMPLETE) + queueSingleDnsCheck(); + break; + case SCAN_RESULTS_AVAILABLE: + updateBssids(); + break; + case WIFI_STATE_CHANGE: + if ((Integer) msg.obj == WifiManager.WIFI_STATE_DISABLING) { + Slog.i(WWS_TAG, "WifiStateDisabling -- Resetting WatchdogState"); + mStatus = new Status(); + } + break; + case MESSAGE_CONTEXT_EVENT: + if (isWatchdogEnabled() && !mBroadcastsEnabled) { + mContext.registerReceiver(mBroadcastReceiver, mIntentFilter); + mBroadcastsEnabled = true; + Slog.i(WWS_TAG, "WifiWatchdogService enabled"); + } else if (!isWatchdogEnabled() && mBroadcastsEnabled) { + mContext.unregisterReceiver(mBroadcastReceiver); + removeMessages(SINGLE_DNS_CHECK); + removeMessages(CHECK_SEQUENCE_STEP); + mBroadcastsEnabled = false; + Slog.i(WWS_TAG, "WifiWatchdogService disabled"); + } break; } } + } - private void handleWifiStateChanged(int wifiState) { - if (wifiState == WifiManager.WIFI_STATE_DISABLED) { - onDisconnected(); - } else if (wifiState == WifiManager.WIFI_STATE_ENABLED) { - onEnabled(); - } - } - }; + public void dump(PrintWriter pw) { + pw.print("WatchdogStatus: "); + pw.print("State " + mStatus.state); + pw.println(", network [" + mStatus.ssid + ", " + mStatus.bssid + "]"); + pw.print("checkCount " + mStatus.numFullDNSchecks); + pw.print(", bssids: " + mStatus.allBssids.size()); + pw.print(", hasCheckMessages? " + + mHandler.hasMessages(WifiWatchdogHandler.CHECK_SEQUENCE_STEP)); + pw.println(" hasSingleCheckMessages? " + + mHandler.hasMessages(WifiWatchdogHandler.SINGLE_DNS_CHECK)); + } /** - * Describes an access point by its SSID and BSSID. - * + * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED */ - private static class AccessPoint { - String ssid; - String bssid; - - /** - * @param ssid cannot be null - * @param bssid cannot be null - */ - AccessPoint(String ssid, String bssid) { - if (ssid == null || bssid == null) { - Slog.e(TAG, String.format("(%s) INVALID ACCESSPOINT: (%s, %s)", - Thread.currentThread().getName(),ssid,bssid)); - } - this.ssid = ssid; - this.bssid = bssid; - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof AccessPoint)) return false; - AccessPoint otherAp = (AccessPoint) o; - - // Either we both have a null, or our SSIDs and BSSIDs are equal - return ssid.equals(otherAp.ssid) && bssid.equals(otherAp.bssid); - } - - @Override - public int hashCode() { - return ssid.hashCode() + bssid.hashCode(); - } - - @Override - public String toString() { - return ssid + " (" + bssid + ")"; - } + private Boolean isWalledGardenTestEnabled() { + return Settings.Secure.getInt(mContentResolver, + Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED, 1) == 1; } /** - * Performs a simple DNS "ping" by sending a "server status" query packet to - * the DNS server. As long as the server replies, we consider it a success. - * <p> - * We do not use a simple hostname lookup because that could be cached and - * the API may not differentiate between a time out and a failure lookup - * (which we really care about). + * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_URL */ - private static class DnsPinger { - - /** Number of bytes for the query */ - private static final int DNS_QUERY_BASE_SIZE = 33; - - /** The DNS port */ - private static final int DNS_PORT = 53; - - /** Used to generate IDs */ - private static Random sRandom = new Random(); - - static boolean isDnsReachable(InetAddress dnsAddress, int timeout) { - DatagramSocket socket = null; - try { - socket = new DatagramSocket(); - - // Set some socket properties - socket.setSoTimeout(timeout); - - byte[] buf = new byte[DNS_QUERY_BASE_SIZE]; - fillQuery(buf); - - // Send the DNS query - - DatagramPacket packet = new DatagramPacket(buf, - buf.length, dnsAddress, DNS_PORT); - socket.send(packet); - - // Wait for reply (blocks for the above timeout) - DatagramPacket replyPacket = new DatagramPacket(buf, buf.length); - socket.receive(replyPacket); - - // If a timeout occurred, an exception would have been thrown. We got a reply! - return true; - - } catch (SocketException e) { - if (V) { - Slog.v(TAG, "DnsPinger.isReachable received SocketException", e); - } - return false; + private String getWalledGardenUrl() { + String url = Settings.Secure.getString(mContentResolver, + Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_URL); + if (TextUtils.isEmpty(url)) + return "http://www.google.com/"; + return url; + } - } catch (UnknownHostException e) { - if (V) { - Slog.v(TAG, "DnsPinger.isReachable is unable to resolve the DNS host", e); - } - return false; + /** + * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_PATTERN + */ + private String getWalledGardenPattern() { + String pattern = Settings.Secure.getString(mContentResolver, + Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_PATTERN); + if (TextUtils.isEmpty(pattern)) + return "<title>.*Google.*</title>"; + return pattern; + } - } catch (SocketTimeoutException e) { - return false; - - } catch (IOException e) { - if (V) { - Slog.v(TAG, "DnsPinger.isReachable got an IOException", e); - } - return false; - - } catch (Exception e) { - if (V) { - Slog.d(TAG, "DnsPinger.isReachable got an unknown exception", e); - } - return false; - } finally { - if (socket != null) { - socket.close(); - } - } - } - - private static void fillQuery(byte[] buf) { - - /* - * See RFC2929 (though the bit tables in there are misleading for - * us. For example, the recursion desired bit is the 0th bit for us, - * but looking there it would appear as the 7th bit of the byte - */ - - // Make sure it's all zeroed out - for (int i = 0; i < buf.length; i++) buf[i] = 0; - - // Form a query for www.android.com - - // [0-1] bytes are an ID, generate random ID for this query - buf[0] = (byte) sRandom.nextInt(256); - buf[1] = (byte) sRandom.nextInt(256); - - // [2-3] bytes are for flags. - buf[2] = 1; // Recursion desired - - // [4-5] bytes are for the query count - buf[5] = 1; // One query - - // [6-7] [8-9] [10-11] are all counts of other fields we don't use - - // [12-15] for www - writeString(buf, 12, "www"); - - // [16-23] for android - writeString(buf, 16, "android"); - - // [24-27] for com - writeString(buf, 24, "com"); - - // [29-30] bytes are for QTYPE, set to 1 - buf[30] = 1; - - // [31-32] bytes are for QCLASS, set to 1 - buf[32] = 1; - } - - private static void writeString(byte[] buf, int startPos, String string) { - int pos = startPos; - - // Write the length first - buf[pos++] = (byte) string.length(); - for (int i = 0; i < string.length(); i++) { - buf[pos++] = (byte) string.charAt(i); - } - } + /** + * @see android.provider.Settings.Secure#WIFI_WATCHDOG_ON + */ + private boolean isWatchdogEnabled() { + return Settings.Secure.getInt(mContentResolver, + Settings.Secure.WIFI_WATCHDOG_ON, 1) == 1; } } diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java index f6dd43a815b6..a2019fcb0ca4 100644 --- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -1131,7 +1131,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub | AccessibilityEvent.TYPE_VIEW_HOVER_ENTER | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT | AccessibilityEvent.TYPE_VIEW_LONG_CLICKED | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED | AccessibilityEvent.TYPE_VIEW_SELECTED - | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED; + | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED + | AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED + | AccessibilityEvent.TYPE_VIEW_SCROLLED; private int mRetrievalAlowingWindowId; diff --git a/services/java/com/android/server/accessibility/TouchExplorer.java b/services/java/com/android/server/accessibility/TouchExplorer.java index aab189a68cda..1af701577f59 100644 --- a/services/java/com/android/server/accessibility/TouchExplorer.java +++ b/services/java/com/android/server/accessibility/TouchExplorer.java @@ -26,7 +26,6 @@ import android.content.Context; import android.os.Handler; import android.os.SystemClock; import android.util.Slog; -import android.util.SparseArray; import android.view.MotionEvent; import android.view.ViewConfiguration; import android.view.WindowManagerPolicy; @@ -73,15 +72,6 @@ public class TouchExplorer implements Explorer { private static final int STATE_DRAGGING = 0x00000002; private static final int STATE_DELEGATING = 0x00000004; - // Human readable symbolic names for the states of the explorer. - private static final SparseArray<String> sStateSymbolicNames = new SparseArray<String>(); - static { - SparseArray<String> symbolicNames = sStateSymbolicNames; - symbolicNames.append(STATE_TOUCH_EXPLORING, "STATE_TOUCH_EXPLORING"); - symbolicNames.append(STATE_DRAGGING, "STATE_DRAGING"); - symbolicNames.append(STATE_DELEGATING, "STATE_DELEGATING"); - } - // Invalid pointer ID. private static final int INVALID_POINTER_ID = -1; @@ -189,7 +179,7 @@ public class TouchExplorer implements Explorer { if (DEBUG) { Slog.d(LOG_TAG_RECEIVED, "Received event: " + event + ", policyFlags=0x" + Integer.toHexString(policyFlags)); - Slog.d(LOG_TAG_STATE, sStateSymbolicNames.get(mCurrentState)); + Slog.d(LOG_TAG_STATE, getStateSymbolicName(mCurrentState)); } // Keep track of the pointers's state. @@ -708,8 +698,7 @@ public class TouchExplorer implements Explorer { private void sendActionDownAndUp(MotionEvent prototype, int policyFlags) { final PointerProperties[] pointerProperties = mTempPointerProperties; final PointerCoords[] pointerCoords = mTempPointerCoords; - final int pointerId = mPointerTracker.getLastReceivedUpPointerId(); - final int pointerIndex = prototype.findPointerIndex(pointerId); + final int pointerIndex = prototype.getActionIndex(); // Send down. prototype.getPointerProperties(pointerIndex, pointerProperties[0]); @@ -884,6 +873,25 @@ public class TouchExplorer implements Explorer { } /** + * Gets the symbolic name of a state. + * + * @param state A state. + * @return The state symbolic name. + */ + private static String getStateSymbolicName(int state) { + switch (state) { + case STATE_TOUCH_EXPLORING: + return "STATE_TOUCH_EXPLORING"; + case STATE_DRAGGING: + return "STATE_DRAGGING"; + case STATE_DELEGATING: + return "STATE_DELEGATING"; + default: + throw new IllegalArgumentException("Unknown state: " + state); + } + } + + /** * Helper class for tracking pointers and more specifically which of * them are currently down, which are active, and which are delivered * to the view hierarchy. The enclosing {@link TouchExplorer} uses the diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java index 911cac2bcc3d..6bb79499d9b7 100644 --- a/services/java/com/android/server/connectivity/Tethering.java +++ b/services/java/com/android/server/connectivity/Tethering.java @@ -189,8 +189,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub { mDnsServers[1] = DNS_DEFAULT_SERVER2; } - public void interfaceLinkStatusChanged(String iface, boolean link) { - if (DEBUG) Log.d(TAG, "interfaceLinkStatusChanged " + iface + ", " + link); + public void interfaceStatusChanged(String iface, boolean up) { + if (DEBUG) Log.d(TAG, "interfaceStatusChanged " + iface + ", " + up); boolean found = false; boolean usb = false; if (isWifi(iface)) { @@ -205,7 +205,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { synchronized (mIfaces) { TetherInterfaceSM sm = mIfaces.get(iface); - if (link) { + if (up) { if (sm == null) { sm = new TetherInterfaceSM(iface, mLooper, usb); mIfaces.put(iface, sm); @@ -220,6 +220,11 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } } + public void interfaceLinkStateChanged(String iface, boolean up) { + if (DEBUG) Log.d(TAG, "interfaceLinkStateChanged " + iface + ", " + up); + interfaceStatusChanged(iface, up); + } + private boolean isUsb(String iface) { for (String regex : mTetherableUsbRegexs) { if (iface.matches(regex)) return true; @@ -689,6 +694,14 @@ public class Tethering extends INetworkManagementEventObserver.Stub { return retVal; } + //TODO: Temporary handling upstream change triggered without + // CONNECTIVITY_ACTION. Only to accomodate interface + // switch during HO. + // @see bug/4455071 + public void handleTetherIfaceChange() { + mTetherMasterSM.sendMessage(TetherMasterSM.CMD_UPSTREAM_CHANGED); + } + class TetherInterfaceSM extends StateMachine { // notification from the master SM that it's not in tether mode static final int CMD_TETHER_MODE_DEAD = 1; diff --git a/services/java/com/android/server/connectivity/Vpn.java b/services/java/com/android/server/connectivity/Vpn.java index 47813f8f7b69..db3b61e5cb17 100644 --- a/services/java/com/android/server/connectivity/Vpn.java +++ b/services/java/com/android/server/connectivity/Vpn.java @@ -173,7 +173,11 @@ public class Vpn extends INetworkManagementEventObserver.Stub { } // INetworkManagementEventObserver.Stub - public void interfaceLinkStatusChanged(String name, boolean up) { + public void interfaceStatusChanged(String name, boolean up) { + } + + // INetworkManagementEventObserver.Stub + public void interfaceLinkStateChanged(String name, boolean up) { } // INetworkManagementEventObserver.Stub diff --git a/tests/BiDiTests/res/layout/view_group_margin_mixed.xml b/tests/BiDiTests/res/layout/view_group_margin_mixed.xml new file mode 100644 index 000000000000..5845b38bbd8a --- /dev/null +++ b/tests/BiDiTests/res/layout/view_group_margin_mixed.xml @@ -0,0 +1,241 @@ +<?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:id="@+id/view_group_margin_mixed" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:background="#FFFFFFFF"> + + <LinearLayout android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <LinearLayout android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layoutDirection="ltr"> + + <LinearLayout android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="#FF0000FF"> + + <FrameLayout + android:layout_width="80dp" + android:layout_height="80dp" + android:layout_marginLeft="0dip" + android:layout_marginRight="0dip" + android:layout_marginTop="0dip" + android:layout_marginBottom="0dip" + android:background="#FFFF0000"> + </FrameLayout> + + <FrameLayout + android:layout_width="80dp" + android:layout_height="80dp" + android:background="#FF00FF00"> + </FrameLayout> + </LinearLayout> + + <LinearLayout android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="#FF000000"> + + <FrameLayout + android:layout_width="80dp" + android:layout_height="80dp" + android:layout_marginLeft="10dip" + android:layout_marginStart="5dip" + android:layout_marginRight="20dip" + android:layout_marginEnd="5dip" + android:layout_marginTop="5dip" + android:layout_marginBottom="5dip" + android:background="#FFFF0000"> + </FrameLayout> + + <FrameLayout + android:layout_width="80dp" + android:layout_height="80dp" + android:background="#FF00FF00"> + </FrameLayout> + </LinearLayout> + + </LinearLayout> + + <LinearLayout android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layoutDirection="rtl"> + + <LinearLayout android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="#FF0000FF"> + + <FrameLayout + android:layout_width="80dp" + android:layout_height="80dp" + android:layout_marginLeft="0dip" + android:layout_marginRight="0dip" + android:layout_marginTop="0dip" + android:layout_marginBottom="0dip" + android:background="#FFFF0000"> + </FrameLayout> + + <FrameLayout + android:layout_width="80dp" + android:layout_height="80dp" + android:background="#FF00FF00"> + </FrameLayout> + </LinearLayout> + + <LinearLayout android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="#FF000000"> + + <FrameLayout + android:layout_width="80dp" + android:layout_height="80dp" + android:layout_marginLeft="10dip" + android:layout_marginStart="5dip" + android:layout_marginRight="20dip" + android:layout_marginEnd="5dip" + android:layout_marginTop="5dip" + android:layout_marginBottom="5dip" + android:background="#FFFF0000"> + </FrameLayout> + + <FrameLayout + android:layout_width="80dp" + android:layout_height="80dp" + android:background="#FF00FF00"> + </FrameLayout> + </LinearLayout> + + </LinearLayout> + + <LinearLayout android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layoutDirection="inherit"> + + <LinearLayout android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="#FF0000FF"> + + <FrameLayout + android:layout_width="80dp" + android:layout_height="80dp" + android:layout_marginLeft="0dip" + android:layout_marginRight="0dip" + android:layout_marginTop="0dip" + android:layout_marginBottom="0dip" + android:background="#FFFF0000"> + </FrameLayout> + + <FrameLayout + android:layout_width="80dp" + android:layout_height="80dp" + android:background="#FF00FF00"> + </FrameLayout> + </LinearLayout> + + <LinearLayout android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="#FF000000"> + + <FrameLayout + android:layout_width="80dp" + android:layout_height="80dp" + android:layout_marginLeft="10dip" + android:layout_marginStart="5dip" + android:layout_marginRight="20dip" + android:layout_marginEnd="5dip" + android:layout_marginTop="5dip" + android:layout_marginBottom="5dip" + android:background="#FFFF0000"> + </FrameLayout> + + <FrameLayout + android:layout_width="80dp" + android:layout_height="80dp" + android:background="#FF00FF00"> + </FrameLayout> + </LinearLayout> + + </LinearLayout> + + <LinearLayout android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layoutDirection="locale"> + + <LinearLayout android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="#FF0000FF"> + + <FrameLayout + android:layout_width="80dp" + android:layout_height="80dp" + android:layout_marginLeft="0dip" + android:layout_marginRight="0dip" + android:layout_marginTop="0dip" + android:layout_marginBottom="0dip" + android:background="#FFFF0000"> + </FrameLayout> + + <FrameLayout + android:layout_width="80dp" + android:layout_height="80dp" + android:background="#FF00FF00"> + </FrameLayout> + </LinearLayout> + + <LinearLayout android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="#FF000000"> + + <FrameLayout + android:layout_width="80dp" + android:layout_height="80dp" + android:layout_marginLeft="10dip" + android:layout_marginStart="5dip" + android:layout_marginRight="20dip" + android:layout_marginEnd="5dip" + android:layout_marginTop="5dip" + android:layout_marginBottom="5dip" + android:background="#FFFF0000"> + </FrameLayout> + + <FrameLayout + android:layout_width="80dp" + android:layout_height="80dp" + android:background="#FF00FF00"> + </FrameLayout> + </LinearLayout> + + </LinearLayout> + + </LinearLayout> + +</FrameLayout>
\ No newline at end of file diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java index c2683c22fbee..b1e494a6c454 100644 --- a/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java +++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java @@ -106,29 +106,34 @@ public class BiDiTestActivity extends Activity { addItem(result, "Linear LTR", BiDiTestLinearLayoutLtr.class, R.id.linear_layout_ltr); addItem(result, "Linear RTL", BiDiTestLinearLayoutRtl.class, R.id.linear_layout_rtl); addItem(result, "Linear LOC", BiDiTestLinearLayoutLocale.class, R.id.linear_layout_locale); - + addItem(result, "Frame LTR", BiDiTestFrameLayoutLtr.class, R.id.frame_layout_ltr); addItem(result, "Frame RTL", BiDiTestFrameLayoutRtl.class, R.id.frame_layout_rtl); addItem(result, "Frame LOC", BiDiTestFrameLayoutLocale.class, R.id.frame_layout_locale); - + addItem(result, "Relative LTR", BiDiTestRelativeLayoutLtr.class, R.id.relative_layout_ltr); addItem(result, "Relative RTL", BiDiTestRelativeLayoutRtl.class, R.id.relative_layout_rtl); - + addItem(result, "Relative2 LTR", BiDiTestRelativeLayout2Ltr.class, R.id.relative_layout_2_ltr); addItem(result, "Relative2 RTL", BiDiTestRelativeLayout2Rtl.class, R.id.relative_layout_2_rtl); addItem(result, "Relative2 LOC", BiDiTestRelativeLayout2Locale.class, R.id.relative_layout_2_locale); - + addItem(result, "Table LTR", BiDiTestTableLayoutLtr.class, R.id.table_layout_ltr); addItem(result, "Table RTL", BiDiTestTableLayoutRtl.class, R.id.table_layout_rtl); addItem(result, "Table LOC", BiDiTestTableLayoutLocale.class, R.id.table_layout_locale); + addItem(result, "ViewPadding", BiDiTestViewPadding.class, R.id.view_padding); + addItem(result, "ViewPadding MIXED", BiDiTestViewPaddingMixed.class, R.id.view_padding_mixed); + + addItem(result, "Padding", BiDiTestViewPadding.class, R.id.view_padding); + addItem(result, "Padding MIXED", BiDiTestViewPaddingMixed.class, R.id.view_padding_mixed); + + addItem(result, "Margin MIXED", BiDiTestViewGroupMarginMixed.class, R.id.view_group_margin_mixed); + addItem(result, "TextView LTR", BiDiTestTextViewLtr.class, R.id.textview_ltr); addItem(result, "TextView RTL", BiDiTestTextViewRtl.class, R.id.textview_rtl); addItem(result, "TextView LOC", BiDiTestTextViewLocale.class, R.id.textview_locale); - addItem(result, "ViewPadding", BiDiTestViewPadding.class, R.id.view_padding); - addItem(result, "ViewPadding MIXED", BiDiTestViewPaddingMixed.class, R.id.view_padding_mixed); - return result; } diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestViewGroupMarginMixed.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestViewGroupMarginMixed.java new file mode 100644 index 000000000000..ce8c380e870b --- /dev/null +++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestViewGroupMarginMixed.java @@ -0,0 +1,17 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +package com.android.bidi; + +import android.app.Fragment; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +public class BiDiTestViewGroupMarginMixed extends Fragment { + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.view_group_margin_mixed, container, false); + } +} diff --git a/tests/CoreTests/MODULE_LICENSE_APACHE2 b/tests/CoreTests/MODULE_LICENSE_APACHE2 deleted file mode 100644 index e69de29bb2d1..000000000000 --- a/tests/CoreTests/MODULE_LICENSE_APACHE2 +++ /dev/null diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/GLTextureViewActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/GLTextureViewActivity.java index 723f3e8e778e..e1ca756fc0d4 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/GLTextureViewActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/GLTextureViewActivity.java @@ -19,13 +19,17 @@ package com.android.test.hwui; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.app.Activity; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.graphics.SurfaceTexture; -import android.opengl.GLES20; +import android.opengl.GLUtils; import android.os.Bundle; import android.util.Log; import android.view.Gravity; import android.view.TextureView; import android.view.View; +import android.view.ViewGroup; import android.widget.FrameLayout; import javax.microedition.khronos.egl.EGL10; @@ -36,6 +40,12 @@ import javax.microedition.khronos.egl.EGLDisplay; import javax.microedition.khronos.egl.EGLSurface; import javax.microedition.khronos.opengles.GL; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; + +import static android.opengl.GLES20.*; + @SuppressWarnings({"UnusedDeclaration"}) public class GLTextureViewActivity extends Activity implements TextureView.SurfaceTextureListener { private RenderThread mRenderThread; @@ -48,12 +58,14 @@ public class GLTextureViewActivity extends Activity implements TextureView.Surfa mTextureView = new TextureView(this); mTextureView.setSurfaceTextureListener(this); - setContentView(mTextureView, new FrameLayout.LayoutParams(500, 400, Gravity.CENTER)); + setContentView(mTextureView, new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, + Gravity.CENTER)); } @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { - mRenderThread = new RenderThread(surface); + mRenderThread = new RenderThread(getResources(), surface); mRenderThread.start(); mTextureView.setCameraDistance(5000); @@ -93,13 +105,12 @@ public class GLTextureViewActivity extends Activity implements TextureView.Surfa private static final String LOG_TAG = "GLTextureView"; static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; - static final int EGL_SURFACE_TYPE = 0x3033; - static final int EGL_SWAP_BEHAVIOR_PRESERVED_BIT = 0x0400; static final int EGL_OPENGL_ES2_BIT = 4; private volatile boolean mFinished; - private SurfaceTexture mSurface; + private final Resources mResources; + private final SurfaceTexture mSurface; private EGL10 mEgl; private EGLDisplay mEglDisplay; @@ -108,26 +119,96 @@ public class GLTextureViewActivity extends Activity implements TextureView.Surfa private EGLSurface mEglSurface; private GL mGL; - RenderThread(SurfaceTexture surface) { + RenderThread(Resources resources, SurfaceTexture surface) { + mResources = resources; mSurface = surface; } + private static final String sSimpleVS = + "attribute vec4 position;\n" + + "attribute vec2 texCoords;\n" + + "varying vec2 outTexCoords;\n" + + "\nvoid main(void) {\n" + + " outTexCoords = texCoords;\n" + + " gl_Position = position;\n" + + "}\n\n"; + private static final String sSimpleFS = + "precision mediump float;\n\n" + + "varying vec2 outTexCoords;\n" + + "uniform sampler2D texture;\n" + + "\nvoid main(void) {\n" + + " gl_FragColor = texture2D(texture, outTexCoords);\n" + + "}\n\n"; + + private static final int FLOAT_SIZE_BYTES = 4; + private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES; + private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0; + private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3; + private final float[] mTriangleVerticesData = { + // X, Y, Z, U, V + -1.0f, -1.0f, 0, 0.f, 0.f, + 1.0f, -1.0f, 0, 1.f, 0.f, + -1.0f, 1.0f, 0, 0.f, 1.f, + 1.0f, 1.0f, 0, 1.f, 1.f, + }; + @Override public void run() { initGL(); + + FloatBuffer triangleVertices = ByteBuffer.allocateDirect(mTriangleVerticesData.length + * FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer(); + triangleVertices.put(mTriangleVerticesData).position(0); + + int texture = loadTexture(R.drawable.large_photo); + int program = buildProgram(sSimpleVS, sSimpleFS); + + int attribPosition = glGetAttribLocation(program, "position"); + checkGlError(); + + int attribTexCoords = glGetAttribLocation(program, "texCoords"); + checkGlError(); + + int uniformTexture = glGetUniformLocation(program, "texture"); + checkGlError(); + + glBindTexture(GL_TEXTURE_2D, texture); + checkGlError(); - float red = 1.0f; + glUseProgram(program); + checkGlError(); + + glEnableVertexAttribArray(attribPosition); + checkGlError(); + + glEnableVertexAttribArray(attribTexCoords); + checkGlError(); + + glUniform1i(texture, 0); + checkGlError(); + while (!mFinished) { checkCurrent(); Log.d(LOG_TAG, "Rendering frame"); - GLES20.glClearColor(red, 0.0f, 0.0f, 1.0f); + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); checkGlError(); - GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); + glClear(GL_COLOR_BUFFER_BIT); checkGlError(); + // drawQuad + triangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET); + glVertexAttribPointer(attribPosition, 3, GL_FLOAT, false, + TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices); + + triangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET); + glVertexAttribPointer(attribTexCoords, 3, GL_FLOAT, false, + TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) { throw new RuntimeException("Cannot swap buffers"); } @@ -138,14 +219,90 @@ public class GLTextureViewActivity extends Activity implements TextureView.Surfa } catch (InterruptedException e) { // Ignore } - - red += 0.021f; - if (red > 1.0f) red = 0.0f; } finishGL(); } + private int loadTexture(int resource) { + int[] textures = new int[1]; + + glActiveTexture(GL_TEXTURE0); + glGenTextures(1, textures, 0); + checkGlError(); + + int texture = textures[0]; + glBindTexture(GL_TEXTURE_2D, texture); + checkGlError(); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + Bitmap bitmap = BitmapFactory.decodeResource(mResources, resource); + + GLUtils.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bitmap, GL_UNSIGNED_BYTE, 0); + checkGlError(); + + bitmap.recycle(); + + return texture; + } + + private int buildProgram(String vertex, String fragment) { + int vertexShader = buildShader(vertex, GL_VERTEX_SHADER); + if (vertexShader == 0) return 0; + + int fragmentShader = buildShader(fragment, GL_FRAGMENT_SHADER); + if (fragmentShader == 0) return 0; + + int program = glCreateProgram(); + glAttachShader(program, vertexShader); + checkGlError(); + + glAttachShader(program, fragmentShader); + checkGlError(); + + glLinkProgram(program); + checkGlError(); + + int[] status = new int[1]; + glGetProgramiv(program, GL_LINK_STATUS, status, 0); + if (status[0] != GL_TRUE) { + String error = glGetProgramInfoLog(program); + Log.d(LOG_TAG, "Error while linking program:\n" + error); + glDeleteShader(vertexShader); + glDeleteShader(fragmentShader); + glDeleteProgram(program); + return 0; + } + + return program; + } + + private int buildShader(String source, int type) { + int shader = glCreateShader(type); + + glShaderSource(shader, source); + checkGlError(); + + glCompileShader(shader); + checkGlError(); + + int[] status = new int[1]; + glGetShaderiv(shader, GL_COMPILE_STATUS, status, 0); + if (status[0] != GL_TRUE) { + String error = glGetShaderInfoLog(shader); + Log.d(LOG_TAG, "Error while compiling shader:\n" + error); + glDeleteShader(shader); + return 0; + } + + return shader; + } + private void checkEglError() { int error = mEgl.eglGetError(); if (error != EGL10.EGL_SUCCESS) { @@ -154,8 +311,8 @@ public class GLTextureViewActivity extends Activity implements TextureView.Surfa } private void checkGlError() { - int error = GLES20.glGetError(); - if (error != GLES20.GL_NO_ERROR) { + int error = glGetError(); + if (error != GL_NO_ERROR) { Log.w(LOG_TAG, "GL error = 0x" + Integer.toHexString(error)); } } diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/TextureViewActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/TextureViewActivity.java index 01ee90a368d1..c857ded8c010 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/TextureViewActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/TextureViewActivity.java @@ -17,7 +17,6 @@ package com.android.test.hwui; import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; import android.app.Activity; import android.graphics.SurfaceTexture; import android.hardware.Camera; diff --git a/voip/java/android/net/sip/ISipSessionListener.aidl b/voip/java/android/net/sip/ISipSessionListener.aidl index 5920bca88647..690700c0f8a9 100644 --- a/voip/java/android/net/sip/ISipSessionListener.aidl +++ b/voip/java/android/net/sip/ISipSessionListener.aidl @@ -72,6 +72,14 @@ interface ISipSessionListener { void onCallBusy(in ISipSession session); /** + * Called when the call is being transferred to a new one. + * + * @param newSession the new session that the call will be transferred to + * @param sessionDescription the new peer's session description + */ + void onCallTransferring(in ISipSession newSession, String sessionDescription); + + /** * Called when an error occurs during session initialization and * termination. * diff --git a/voip/java/android/net/sip/SipAudioCall.java b/voip/java/android/net/sip/SipAudioCall.java index b46f8268a0fd..2666b69789d0 100644 --- a/voip/java/android/net/sip/SipAudioCall.java +++ b/voip/java/android/net/sip/SipAudioCall.java @@ -170,6 +170,7 @@ public class SipAudioCall { private SipProfile mLocalProfile; private SipAudioCall.Listener mListener; private SipSession mSipSession; + private SipSession mTransferringSession; private long mSessionId = System.currentTimeMillis(); private String mPeerSd; @@ -347,6 +348,27 @@ public class SipAudioCall { } } + private synchronized void transferToNewSession() { + if (mTransferringSession == null) return; + SipSession origin = mSipSession; + mSipSession = mTransferringSession; + mTransferringSession = null; + + // stop the replaced call. + if (mAudioStream != null) { + mAudioStream.join(null); + } else { + try { + mAudioStream = new AudioStream(InetAddress.getByName( + getLocalIp())); + } catch (Throwable t) { + Log.i(TAG, "transferToNewSession(): " + t); + } + } + if (origin != null) origin.endCall(); + startAudio(); + } + private SipSession.Listener createListener() { return new SipSession.Listener() { @Override @@ -404,6 +426,13 @@ public class SipAudioCall { mPeerSd = sessionDescription; Log.v(TAG, "onCallEstablished()" + mPeerSd); + // TODO: how to notify the UI that the remote party is changed + if ((mTransferringSession != null) + && (session == mTransferringSession)) { + transferToNewSession(); + return; + } + Listener listener = mListener; if (listener != null) { try { @@ -420,7 +449,17 @@ public class SipAudioCall { @Override public void onCallEnded(SipSession session) { - Log.d(TAG, "sip call ended: " + session); + Log.d(TAG, "sip call ended: " + session + " mSipSession:" + mSipSession); + // reset the trasnferring session if it is the one. + if (session == mTransferringSession) { + mTransferringSession = null; + return; + } + // or ignore the event if the original session is being + // transferred to the new one. + if ((mTransferringSession != null) || + (session != mSipSession)) return; + Listener listener = mListener; if (listener != null) { try { @@ -489,6 +528,22 @@ public class SipAudioCall { public void onRegistrationDone(SipSession session, int duration) { // irrelevant } + + @Override + public void onCallTransferring(SipSession newSession, + String sessionDescription) { + Log.v(TAG, "onCallTransferring mSipSession:" + + mSipSession + " newSession:" + newSession); + mTransferringSession = newSession; + // session changing request + try { + String answer = createAnswer(sessionDescription).encode(); + newSession.answerCall(answer, SESSION_TIMEOUT); + } catch (Throwable e) { + Log.e(TAG, "onCallTransferring()", e); + newSession.endCall(); + } + } }; } diff --git a/voip/java/android/net/sip/SipSession.java b/voip/java/android/net/sip/SipSession.java index 5629b3c56d50..5ba162606c61 100644 --- a/voip/java/android/net/sip/SipSession.java +++ b/voip/java/android/net/sip/SipSession.java @@ -160,6 +160,17 @@ public final class SipSession { } /** + * Called when the call is being transferred to a new one. + * + * @hide + * @param newSession the new session that the call will be transferred to + * @param sessionDescription the new peer's session description + */ + public void onCallTransferring(SipSession newSession, + String sessionDescription) { + } + + /** * Called when an error occurs during session initialization and * termination. * @@ -489,6 +500,16 @@ public final class SipSession { } } + public void onCallTransferring(ISipSession session, + String sessionDescription) { + if (mListener != null) { + mListener.onCallTransferring( + new SipSession(session, SipSession.this.mListener), + sessionDescription); + + } + } + public void onCallChangeFailed(ISipSession session, int errorCode, String message) { if (mListener != null) { diff --git a/voip/java/android/net/sip/SipSessionAdapter.java b/voip/java/android/net/sip/SipSessionAdapter.java index 86aca3798b03..f538983cf06e 100644 --- a/voip/java/android/net/sip/SipSessionAdapter.java +++ b/voip/java/android/net/sip/SipSessionAdapter.java @@ -42,6 +42,10 @@ public class SipSessionAdapter extends ISipSessionListener.Stub { public void onCallBusy(ISipSession session) { } + public void onCallTransferring(ISipSession session, + String sessionDescription) { + } + public void onCallChangeFailed(ISipSession session, int errorCode, String message) { } diff --git a/voip/java/com/android/server/sip/SipHelper.java b/voip/java/com/android/server/sip/SipHelper.java index 018e6de5c31b..47950e30afa8 100644 --- a/voip/java/com/android/server/sip/SipHelper.java +++ b/voip/java/com/android/server/sip/SipHelper.java @@ -314,7 +314,7 @@ class SipHelper { } } - private ServerTransaction getServerTransaction(RequestEvent event) + public ServerTransaction getServerTransaction(RequestEvent event) throws SipException { ServerTransaction transaction = event.getServerTransaction(); if (transaction == null) { diff --git a/voip/java/com/android/server/sip/SipSessionGroup.java b/voip/java/com/android/server/sip/SipSessionGroup.java index 2d0dd9c0c7dc..481e306d3b00 100644 --- a/voip/java/com/android/server/sip/SipSessionGroup.java +++ b/voip/java/com/android/server/sip/SipSessionGroup.java @@ -21,6 +21,8 @@ import gov.nist.javax.sip.clientauthutils.UserCredentials; import gov.nist.javax.sip.header.SIPHeaderNames; import gov.nist.javax.sip.header.ProxyAuthenticate; import gov.nist.javax.sip.header.WWWAuthenticate; +import gov.nist.javax.sip.header.extensions.ReferredByHeader; +import gov.nist.javax.sip.header.extensions.ReplacesHeader; import gov.nist.javax.sip.message.SIPMessage; import android.net.sip.ISipSession; @@ -365,24 +367,85 @@ class SipSessionGroup implements SipListener { super(listener); } + private SipSessionImpl createNewSession(RequestEvent event, + ISipSessionListener listener, ServerTransaction transaction) + throws SipException { + SipSessionImpl newSession = new SipSessionImpl(listener); + newSession.mServerTransaction = transaction; + newSession.mState = SipSession.State.INCOMING_CALL; + newSession.mDialog = newSession.mServerTransaction.getDialog(); + newSession.mInviteReceived = event; + newSession.mPeerProfile = createPeerProfile(event.getRequest()); + newSession.mPeerSessionDescription = + extractContent(event.getRequest()); + return newSession; + } + + private int processInviteWithReplaces(RequestEvent event, + ReplacesHeader replaces) { + String callId = replaces.getCallId(); + SipSessionImpl session = mSessionMap.get(callId); + if (session == null) { + return Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST; + } + + Dialog dialog = session.mDialog; + if (dialog == null) return Response.DECLINE; + + if (!dialog.getLocalTag().equals(replaces.getToTag()) || + !dialog.getRemoteTag().equals(replaces.getFromTag())) { + // No match is found, returns 481. + return Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST; + } + + ReferredByHeader referredBy = (ReferredByHeader) event.getRequest() + .getHeader(ReferredByHeader.NAME); + if ((referredBy == null) || + !dialog.getRemoteParty().equals(referredBy.getAddress())) { + return Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST; + } + return Response.OK; + } + + private void processNewInviteRequest(RequestEvent event) + throws SipException { + ReplacesHeader replaces = (ReplacesHeader) event.getRequest() + .getHeader(ReplacesHeader.NAME); + SipSessionImpl newSession = null; + if (replaces != null) { + int response = processInviteWithReplaces(event, replaces); + if (DEBUG) { + Log.v(TAG, "ReplacesHeader: " + replaces + + " response=" + response); + } + if (response == Response.OK) { + SipSessionImpl replacedSession = + mSessionMap.get(replaces.getCallId()); + // got INVITE w/ replaces request. + newSession = createNewSession(event, + replacedSession.mProxy.getListener(), + mSipHelper.getServerTransaction(event)); + newSession.mProxy.onCallTransferring(newSession, + newSession.mPeerSessionDescription); + } else { + mSipHelper.sendResponse(event, response); + } + } else { + // New Incoming call. + newSession = createNewSession(event, mProxy, + mSipHelper.sendRinging(event, generateTag())); + mProxy.onRinging(newSession, newSession.mPeerProfile, + newSession.mPeerSessionDescription); + } + if (newSession != null) addSipSession(newSession); + } + public boolean process(EventObject evt) throws SipException { if (isLoggable(this, evt)) Log.d(TAG, " ~~~~~ " + this + ": " + SipSession.State.toString(mState) + ": processing " + log(evt)); if (isRequestEvent(Request.INVITE, evt)) { - RequestEvent event = (RequestEvent) evt; - SipSessionImpl newSession = new SipSessionImpl(mProxy); - newSession.mState = SipSession.State.INCOMING_CALL; - newSession.mServerTransaction = mSipHelper.sendRinging(event, - generateTag()); - newSession.mDialog = newSession.mServerTransaction.getDialog(); - newSession.mInviteReceived = event; - newSession.mPeerProfile = createPeerProfile(event.getRequest()); - newSession.mPeerSessionDescription = - extractContent(event.getRequest()); - addSipSession(newSession); - mProxy.onRinging(newSession, newSession.mPeerProfile, - newSession.mPeerSessionDescription); + processNewInviteRequest((RequestEvent) evt); return true; } else if (isRequestEvent(Request.OPTIONS, evt)) { mSipHelper.sendResponse((RequestEvent) evt, Response.OK); diff --git a/voip/java/com/android/server/sip/SipSessionListenerProxy.java b/voip/java/com/android/server/sip/SipSessionListenerProxy.java index f8be0a82b94d..8655a3ac3a51 100644 --- a/voip/java/com/android/server/sip/SipSessionListenerProxy.java +++ b/voip/java/com/android/server/sip/SipSessionListenerProxy.java @@ -110,6 +110,20 @@ class SipSessionListenerProxy extends ISipSessionListener.Stub { }); } + public void onCallTransferring(final ISipSession newSession, + final String sessionDescription) { + if (mListener == null) return; + proxy(new Runnable() { + public void run() { + try { + mListener.onCallTransferring(newSession, sessionDescription); + } catch (Throwable t) { + handle(t, "onCallTransferring()"); + } + } + }); + } + public void onCallBusy(final ISipSession session) { if (mListener == null) return; proxy(new Runnable() { |