diff options
83 files changed, 3365 insertions, 1704 deletions
diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java index 6e99899..652ad23 100644 --- a/core/java/android/app/FragmentTransaction.java +++ b/core/java/android/app/FragmentTransaction.java @@ -106,7 +106,7 @@ public abstract class FragmentTransaction { public abstract FragmentTransaction detach(Fragment fragment); /** - * Re-attach a fragment after it had previously been deatched from + * Re-attach a fragment after it had previously been detached from * the UI with {@link #detach(Fragment)}. This * causes its view hierarchy to be re-created, attached to the UI, * and displayed. diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index ab82531..49ed27b 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -1071,7 +1071,7 @@ public class DevicePolicyManager { public static final int WIPE_EXTERNAL_STORAGE = 0x0001; /** - * Ask the user date be wiped. This will cause the device to reboot, + * Ask the user data be wiped. This will cause the device to reboot, * erasing all user data while next booting up. External storage such * as SD cards will be also erased if the flag {@link #WIPE_EXTERNAL_STORAGE} * is set. diff --git a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java index a9b7176..f665a00 100644 --- a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java +++ b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java @@ -91,7 +91,7 @@ public class BluetoothTetheringDataTracker extends BaseNetworkStateTracker { return sInstance; } - public Object Clone() throws CloneNotSupportedException { + public Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); } diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 3db9ddb..9a67dc5 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -1680,15 +1680,7 @@ public class Resources { remainder = languageTag.substring(separator); } - if ("id".equals(language)) { - return "in" + remainder; - } else if ("yi".equals(language)) { - return "ji" + remainder; - } else if ("he".equals(language)) { - return "iw" + remainder; - } else { - return languageTag; - } + return Locale.adjustLanguageCode(language) + remainder; } /** diff --git a/core/java/android/hardware/ConsumerIrManager.java b/core/java/android/hardware/ConsumerIrManager.java index 77087814..6d29212 100644 --- a/core/java/android/hardware/ConsumerIrManager.java +++ b/core/java/android/hardware/ConsumerIrManager.java @@ -65,7 +65,7 @@ public final class ConsumerIrManager { } /** - * Tansmit and infrared pattern + * Transmit an infrared pattern * <p> * This method is synchronous; when it returns the pattern has * been transmitted. Only patterns shorter than 2 seconds will diff --git a/core/java/android/net/EthernetDataTracker.java b/core/java/android/net/EthernetDataTracker.java index cc8c771..5ed2409 100644 --- a/core/java/android/net/EthernetDataTracker.java +++ b/core/java/android/net/EthernetDataTracker.java @@ -186,7 +186,7 @@ public class EthernetDataTracker extends BaseNetworkStateTracker { return sInstance; } - public Object Clone() throws CloneNotSupportedException { + public Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); } diff --git a/core/java/android/net/http/CertificateChainValidator.java b/core/java/android/net/http/CertificateChainValidator.java index d06355d..bf3fe02 100644 --- a/core/java/android/net/http/CertificateChainValidator.java +++ b/core/java/android/net/http/CertificateChainValidator.java @@ -16,6 +16,9 @@ package android.net.http; +import com.android.org.conscrypt.SSLParametersImpl; +import com.android.org.conscrypt.TrustManagerImpl; + import android.util.Slog; import java.io.ByteArrayInputStream; @@ -37,7 +40,7 @@ import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509ExtendedTrustManager; +import javax.net.ssl.X509TrustManager; /** * Class responsible for all server certificate validation functionality @@ -60,7 +63,7 @@ public class CertificateChainValidator { .getDefaultHostnameVerifier(); } - private X509ExtendedTrustManager mTrustManager; + private X509TrustManager mTrustManager; /** * @return The singleton instance of the certificates chain validator @@ -78,8 +81,8 @@ public class CertificateChainValidator { TrustManagerFactory tmf = TrustManagerFactory.getInstance("X.509"); tmf.init((KeyStore) null); for (TrustManager tm : tmf.getTrustManagers()) { - if (tm instanceof X509ExtendedTrustManager) { - mTrustManager = (X509ExtendedTrustManager) tm; + if (tm instanceof X509TrustManager) { + mTrustManager = (X509TrustManager) tm; } } } catch (NoSuchAlgorithmException e) { @@ -90,7 +93,7 @@ public class CertificateChainValidator { if (mTrustManager == null) { throw new RuntimeException( - "None of the X.509 TrustManagers are X509ExtendedTrustManager"); + "None of the X.509 TrustManagers are X509TrustManager"); } } @@ -225,8 +228,13 @@ public class CertificateChainValidator { } try { - getInstance().getTrustManager().checkServerTrusted(chain, authType, - new DelegatingSocketWrapper(domain)); + X509TrustManager x509TrustManager = SSLParametersImpl.getDefaultX509TrustManager(); + if (x509TrustManager instanceof TrustManagerImpl) { + TrustManagerImpl trustManager = (TrustManagerImpl) x509TrustManager; + trustManager.checkServerTrusted(chain, authType, domain); + } else { + x509TrustManager.checkServerTrusted(chain, authType); + } return null; // No errors. } catch (GeneralSecurityException e) { if (HttpLog.LOGV) { @@ -238,9 +246,9 @@ public class CertificateChainValidator { } /** - * Returns the platform default {@link X509ExtendedTrustManager}. + * Returns the platform default {@link X509TrustManager}. */ - private X509ExtendedTrustManager getTrustManager() { + private X509TrustManager getTrustManager() { return mTrustManager; } @@ -268,4 +276,4 @@ public class CertificateChainValidator { throw new SSLHandshakeException(errorMessage); } -}
\ No newline at end of file +} diff --git a/core/java/android/net/http/DelegatingSSLSession.java b/core/java/android/net/http/DelegatingSSLSession.java index ff75b24..98fbe21 100644 --- a/core/java/android/net/http/DelegatingSSLSession.java +++ b/core/java/android/net/http/DelegatingSSLSession.java @@ -24,12 +24,11 @@ import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSessionContext; import javax.net.ssl.SSLSocket; -import javax.net.ssl.X509ExtendedTrustManager; +import javax.net.ssl.X509TrustManager; /** - * This is used when only a {@code hostname} is available but usage of the new API - * {@link X509ExtendedTrustManager#checkServerTrusted(X509Certificate[], String, Socket)} - * requires a {@link SSLSocket}. + * This is only used when a {@code certificate} is available but usage + * requires a {@link SSLSession}. * * @hide */ @@ -37,19 +36,6 @@ public class DelegatingSSLSession implements SSLSession { protected DelegatingSSLSession() { } - public static class HostnameWrap extends DelegatingSSLSession { - private final String mHostname; - - public HostnameWrap(String hostname) { - mHostname = hostname; - } - - @Override - public String getPeerHost() { - return mHostname; - } - } - public static class CertificateWrap extends DelegatingSSLSession { private final Certificate mCertificate; @@ -169,4 +155,4 @@ public class DelegatingSSLSession implements SSLSession { public void removeValue(String name) { throw new UnsupportedOperationException(); } -}
\ No newline at end of file +} diff --git a/core/java/android/net/http/DelegatingSocketWrapper.java b/core/java/android/net/http/DelegatingSocketWrapper.java deleted file mode 100644 index 230d017..0000000 --- a/core/java/android/net/http/DelegatingSocketWrapper.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net.http; - -import java.io.IOException; - -import javax.net.ssl.HandshakeCompletedListener; -import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSocket; -import javax.net.ssl.X509ExtendedTrustManager; - -/** - * This is used when only a {@code hostname} is available for - * {@link X509ExtendedTrustManager#checkServerTrusted(java.security.cert.X509Certificate[], String, Socket)} - * but we want to use the new API that requires a {@link SSLSocket}. - */ -class DelegatingSocketWrapper extends SSLSocket { - private String hostname; - - public DelegatingSocketWrapper(String hostname) { - this.hostname = hostname; - } - - @Override - public String[] getSupportedCipherSuites() { - throw new UnsupportedOperationException(); - } - - @Override - public String[] getEnabledCipherSuites() { - throw new UnsupportedOperationException(); - } - - @Override - public void setEnabledCipherSuites(String[] suites) { - throw new UnsupportedOperationException(); - } - - @Override - public String[] getSupportedProtocols() { - throw new UnsupportedOperationException(); - } - - @Override - public String[] getEnabledProtocols() { - throw new UnsupportedOperationException(); - } - - @Override - public void setEnabledProtocols(String[] protocols) { - throw new UnsupportedOperationException(); - } - - @Override - public SSLSession getSession() { - return new DelegatingSSLSession.HostnameWrap(hostname); - } - - @Override - public void addHandshakeCompletedListener(HandshakeCompletedListener listener) { - throw new UnsupportedOperationException(); - } - - @Override - public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) { - throw new UnsupportedOperationException(); - } - - @Override - public void startHandshake() throws IOException { - throw new UnsupportedOperationException(); - } - - @Override - public void setUseClientMode(boolean mode) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean getUseClientMode() { - throw new UnsupportedOperationException(); - } - - @Override - public void setNeedClientAuth(boolean need) { - throw new UnsupportedOperationException(); - } - - @Override - public void setWantClientAuth(boolean want) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean getNeedClientAuth() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean getWantClientAuth() { - throw new UnsupportedOperationException(); - } - - @Override - public void setEnableSessionCreation(boolean flag) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean getEnableSessionCreation() { - throw new UnsupportedOperationException(); - } -}
\ No newline at end of file diff --git a/core/java/android/net/http/X509TrustManagerExtensions.java b/core/java/android/net/http/X509TrustManagerExtensions.java index d730a7b..e8ccc2b 100644 --- a/core/java/android/net/http/X509TrustManagerExtensions.java +++ b/core/java/android/net/http/X509TrustManagerExtensions.java @@ -24,7 +24,6 @@ import java.util.List; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSocket; -import javax.net.ssl.X509ExtendedTrustManager; import javax.net.ssl.X509TrustManager; /** @@ -34,13 +33,6 @@ import javax.net.ssl.X509TrustManager; * verification of certificate chains after they have been successfully verified * by the platform. * </p> - * <p> - * If the returned certificate list is not needed, see also - * {@code X509ExtendedTrustManager#checkServerTrusted(X509Certificate[], String, java.net.Socket)} - * where an {@link SSLSocket} can be used to verify the given hostname during - * handshake using - * {@code SSLParameters#setEndpointIdentificationAlgorithm(String)}. - * </p> */ public class X509TrustManagerExtensions { @@ -73,7 +65,6 @@ public class X509TrustManagerExtensions { */ public List<X509Certificate> checkServerTrusted(X509Certificate[] chain, String authType, String host) throws CertificateException { - return mDelegate.checkServerTrusted(chain, authType, - new DelegatingSSLSession.HostnameWrap(host)); + return mDelegate.checkServerTrusted(chain, authType, host); } } diff --git a/core/java/android/os/Message.java b/core/java/android/os/Message.java index 51203a4..5ae03e1 100644 --- a/core/java/android/os/Message.java +++ b/core/java/android/os/Message.java @@ -343,7 +343,7 @@ public final class Message implements Parcelable { } /** - * Sets a Bundle of arbitrary data values. Use arg1 and arg1 members + * Sets a Bundle of arbitrary data values. Use arg1 and arg2 members * as a lower cost way to send a few simple integer values, if you can. * @see #getData() * @see #peekData() diff --git a/core/java/android/text/format/Time.java b/core/java/android/text/format/Time.java index f34e746..aa6ad20 100644 --- a/core/java/android/text/format/Time.java +++ b/core/java/android/text/format/Time.java @@ -16,19 +16,38 @@ package android.text.format; -import android.content.res.Resources; +import android.util.TimeFormatException; +import java.io.IOException; import java.util.Locale; import java.util.TimeZone; -import libcore.icu.LocaleData; +import libcore.util.ZoneInfo; +import libcore.util.ZoneInfoDB; /** * An alternative to the {@link java.util.Calendar} and * {@link java.util.GregorianCalendar} classes. An instance of the Time class represents * a moment in time, specified with second precision. It is modelled after - * struct tm, and in fact, uses struct tm to implement most of the - * functionality. + * struct tm. This class is not thread-safe and does not consider leap seconds. + * + * <p>This class has a number of issues and it is recommended that + * {@link java.util.GregorianCalendar} is used instead. + * + * <p>Known issues: + * <ul> + * <li>For historical reasons when performing time calculations all arithmetic currently takes + * place using 32-bit integers. This limits the reliable time range representable from 1902 + * until 2037.See the wikipedia article on the + * <a href="http://en.wikipedia.org/wiki/Year_2038_problem">Year 2038 problem</a> for details. + * Do not rely on this behavior; it may change in the future. + * </li> + * <li>Calling {@link #switchTimezone(String)} on a date that cannot exist, such as a wall time + * that was skipped due to a DST transition, will result in a date in 1969 (i.e. -1, or 1 second + * before 1st Jan 1970 UTC).</li> + * <li>Much of the formatting / parsing assumes ASCII text and is therefore not suitable for + * use with non-ASCII scripts.</li> + * </ul> */ public class Time { private static final String Y_M_D_T_H_M_S_000 = "%Y-%m-%dT%H:%M:%S.000"; @@ -106,7 +125,7 @@ public class Time { public int isDst; /** - * Offset from UTC (in seconds). + * Offset in seconds from UTC including any DST offset. */ public long gmtoff; @@ -137,41 +156,20 @@ public class Time { public static final int FRIDAY = 5; public static final int SATURDAY = 6; - /* - * The Locale for which date formatting strings have been loaded. - */ - private static Locale sLocale; - private static String[] sShortMonths; - private static String[] sLongMonths; - private static String[] sLongStandaloneMonths; - private static String[] sShortWeekdays; - private static String[] sLongWeekdays; - private static String sTimeOnlyFormat; - private static String sDateOnlyFormat; - private static String sDateTimeFormat; - private static String sAm; - private static String sPm; - private static char sZeroDigit; - - // Referenced by native code. - private static String sDateCommand = "%a %b %e %H:%M:%S %Z %Y"; + // An object that is reused for date calculations. + private TimeCalculator calculator; /** * Construct a Time object in the timezone named by the string * argument "timezone". The time is initialized to Jan 1, 1970. - * @param timezone string containing the timezone to use. + * @param timezoneId string containing the timezone to use. * @see TimeZone */ - public Time(String timezone) { - if (timezone == null) { - throw new NullPointerException("timezone is null!"); + public Time(String timezoneId) { + if (timezoneId == null) { + throw new NullPointerException("timezoneId is null!"); } - this.timezone = timezone; - this.year = 1970; - this.monthDay = 1; - // Set the daylight-saving indicator to the unknown value -1 so that - // it will be recomputed. - this.isDst = -1; + initialize(timezoneId); } /** @@ -179,7 +177,7 @@ public class Time { * Jan 1, 1970. */ public Time() { - this(TimeZone.getDefault().getID()); + initialize(TimeZone.getDefault().getID()); } /** @@ -189,9 +187,23 @@ public class Time { * @param other */ public Time(Time other) { + initialize(other.timezone); set(other); } + /** Initialize the Time to 00:00:00 1/1/1970 in the specified timezone. */ + private void initialize(String timezoneId) { + this.timezone = timezoneId; + this.year = 1970; + this.monthDay = 1; + // Set the daylight-saving indicator to the unknown value -1 so that + // it will be recomputed. + this.isDst = -1; + + // A reusable object that performs the date/time calculations. + calculator = new TimeCalculator(timezoneId); + } + /** * Ensures the values in each field are in range. For example if the * current value of this calendar is March 32, normalize() will convert it @@ -208,14 +220,26 @@ public class Time { * * @return the UTC milliseconds since the epoch */ - native public long normalize(boolean ignoreDst); + public long normalize(boolean ignoreDst) { + calculator.copyFieldsFromTime(this); + long timeInMillis = calculator.toMillis(ignoreDst); + calculator.copyFieldsToTime(this); + return timeInMillis; + } /** * Convert this time object so the time represented remains the same, but is * instead located in a different timezone. This method automatically calls - * normalize() in some cases + * normalize() in some cases. + * + * <p>This method can return incorrect results if the date / time cannot be normalized. */ - native public void switchTimezone(String timezone); + public void switchTimezone(String timezone) { + calculator.copyFieldsFromTime(this); + calculator.switchTimeZone(timezone); + calculator.copyFieldsToTime(this); + this.timezone = timezone; + } private static final int[] DAYS_PER_MONTH = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; @@ -265,13 +289,13 @@ public class Time { /** * Clears all values, setting the timezone to the given timezone. Sets isDst * to a negative value to mean "unknown". - * @param timezone the timezone to use. + * @param timezoneId the timezone to use. */ - public void clear(String timezone) { - if (timezone == null) { + public void clear(String timezoneId) { + if (timezoneId == null) { throw new NullPointerException("timezone is null!"); } - this.timezone = timezone; + this.timezone = timezoneId; this.allDay = false; this.second = 0; this.minute = 0; @@ -304,12 +328,12 @@ public class Time { } else if (b == null) { throw new NullPointerException("b == null"); } + a.calculator.copyFieldsFromTime(a); + b.calculator.copyFieldsFromTime(b); - return nativeCompare(a, b); + return TimeCalculator.compare(a.calculator, b.calculator); } - private static native int nativeCompare(Time a, Time b); - /** * Print the current value given the format string provided. See man * strftime for what means what. The final string must be less than 256 @@ -318,61 +342,21 @@ public class Time { * @return a String containing the current time expressed in the current locale. */ public String format(String format) { - synchronized (Time.class) { - Locale locale = Locale.getDefault(); - - if (sLocale == null || locale == null || !(locale.equals(sLocale))) { - LocaleData localeData = LocaleData.get(locale); - - sAm = localeData.amPm[0]; - sPm = localeData.amPm[1]; - sZeroDigit = localeData.zeroDigit; - - sShortMonths = localeData.shortMonthNames; - sLongMonths = localeData.longMonthNames; - sLongStandaloneMonths = localeData.longStandAloneMonthNames; - sShortWeekdays = localeData.shortWeekdayNames; - sLongWeekdays = localeData.longWeekdayNames; - - Resources r = Resources.getSystem(); - sTimeOnlyFormat = r.getString(com.android.internal.R.string.time_of_day); - sDateOnlyFormat = r.getString(com.android.internal.R.string.month_day_year); - sDateTimeFormat = r.getString(com.android.internal.R.string.date_and_time); - - sLocale = locale; - } - - String result = format1(format); - if (sZeroDigit != '0') { - result = localizeDigits(result); - } - return result; - } - } - - native private String format1(String format); - - // TODO: unify this with java.util.Formatter's copy. - private String localizeDigits(String s) { - int length = s.length(); - int offsetToLocalizedDigits = sZeroDigit - '0'; - StringBuilder result = new StringBuilder(length); - for (int i = 0; i < length; ++i) { - char ch = s.charAt(i); - if (ch >= '0' && ch <= '9') { - ch += offsetToLocalizedDigits; - } - result.append(ch); - } - return result.toString(); + calculator.copyFieldsFromTime(this); + return calculator.format(format); } - /** * Return the current time in YYYYMMDDTHHMMSS<tz> format */ @Override - native public String toString(); + public String toString() { + // toString() uses its own TimeCalculator rather than the shared one. Otherwise crazy stuff + // happens during debugging when the debugger calls toString(). + TimeCalculator calculator = new TimeCalculator(this.timezone); + calculator.copyFieldsFromTime(this); + return calculator.toStringInternal(); + } /** * Parses a date-time string in either the RFC 2445 format or an abbreviated @@ -414,7 +398,7 @@ public class Time { if (s == null) { throw new NullPointerException("time string is null"); } - if (nativeParse(s)) { + if (parseInternal(s)) { timezone = TIMEZONE_UTC; return true; } @@ -424,7 +408,94 @@ public class Time { /** * Parse a time in the current zone in YYYYMMDDTHHMMSS format. */ - native private boolean nativeParse(String s); + private boolean parseInternal(String s) { + int len = s.length(); + if (len < 8) { + throw new TimeFormatException("String is too short: \"" + s + + "\" Expected at least 8 characters."); + } + + boolean inUtc = false; + + // year + int n = getChar(s, 0, 1000); + n += getChar(s, 1, 100); + n += getChar(s, 2, 10); + n += getChar(s, 3, 1); + year = n; + + // month + n = getChar(s, 4, 10); + n += getChar(s, 5, 1); + n--; + month = n; + + // day of month + n = getChar(s, 6, 10); + n += getChar(s, 7, 1); + monthDay = n; + + if (len > 8) { + if (len < 15) { + throw new TimeFormatException( + "String is too short: \"" + s + + "\" If there are more than 8 characters there must be at least" + + " 15."); + } + checkChar(s, 8, 'T'); + allDay = false; + + // hour + n = getChar(s, 9, 10); + n += getChar(s, 10, 1); + hour = n; + + // min + n = getChar(s, 11, 10); + n += getChar(s, 12, 1); + minute = n; + + // sec + n = getChar(s, 13, 10); + n += getChar(s, 14, 1); + second = n; + + if (len > 15) { + // Z + checkChar(s, 15, 'Z'); + inUtc = true; + } + } else { + allDay = true; + hour = 0; + minute = 0; + second = 0; + } + + weekDay = 0; + yearDay = 0; + isDst = -1; + gmtoff = 0; + return inUtc; + } + + private void checkChar(String s, int spos, char expected) { + char c = s.charAt(spos); + if (c != expected) { + throw new TimeFormatException(String.format( + "Unexpected character 0x%02d at pos=%d. Expected 0x%02d (\'%c\').", + (int) c, spos, (int) expected, expected)); + } + } + + private static int getChar(String s, int spos, int mul) { + char c = s.charAt(spos); + if (Character.isDigit(c)) { + return Character.getNumericValue(c) * mul; + } else { + throw new TimeFormatException("Parse error at pos=" + spos); + } + } /** * Parse a time in RFC 3339 format. This method also parses simple dates @@ -461,14 +532,140 @@ public class Time { if (s == null) { throw new NullPointerException("time string is null"); } - if (nativeParse3339(s)) { + if (parse3339Internal(s)) { timezone = TIMEZONE_UTC; return true; } return false; } - native private boolean nativeParse3339(String s); + private boolean parse3339Internal(String s) { + int len = s.length(); + if (len < 10) { + throw new TimeFormatException("String too short --- expected at least 10 characters."); + } + boolean inUtc = false; + + // year + int n = getChar(s, 0, 1000); + n += getChar(s, 1, 100); + n += getChar(s, 2, 10); + n += getChar(s, 3, 1); + year = n; + + checkChar(s, 4, '-'); + + // month + n = getChar(s, 5, 10); + n += getChar(s, 6, 1); + --n; + month = n; + + checkChar(s, 7, '-'); + + // day + n = getChar(s, 8, 10); + n += getChar(s, 9, 1); + monthDay = n; + + if (len >= 19) { + // T + checkChar(s, 10, 'T'); + allDay = false; + + // hour + n = getChar(s, 11, 10); + n += getChar(s, 12, 1); + + // Note that this.hour is not set here. It is set later. + int hour = n; + + checkChar(s, 13, ':'); + + // minute + n = getChar(s, 14, 10); + n += getChar(s, 15, 1); + // Note that this.minute is not set here. It is set later. + int minute = n; + + checkChar(s, 16, ':'); + + // second + n = getChar(s, 17, 10); + n += getChar(s, 18, 1); + second = n; + + // skip the '.XYZ' -- we don't care about subsecond precision. + + int tzIndex = 19; + if (tzIndex < len && s.charAt(tzIndex) == '.') { + do { + tzIndex++; + } while (tzIndex < len && Character.isDigit(s.charAt(tzIndex))); + } + + int offset = 0; + if (len > tzIndex) { + char c = s.charAt(tzIndex); + // NOTE: the offset is meant to be subtracted to get from local time + // to UTC. we therefore use 1 for '-' and -1 for '+'. + switch (c) { + case 'Z': + // Zulu time -- UTC + offset = 0; + break; + case '-': + offset = 1; + break; + case '+': + offset = -1; + break; + default: + throw new TimeFormatException(String.format( + "Unexpected character 0x%02d at position %d. Expected + or -", + (int) c, tzIndex)); + } + inUtc = true; + + if (offset != 0) { + if (len < tzIndex + 6) { + throw new TimeFormatException( + String.format("Unexpected length; should be %d characters", + tzIndex + 6)); + } + + // hour + n = getChar(s, tzIndex + 1, 10); + n += getChar(s, tzIndex + 2, 1); + n *= offset; + hour += n; + + // minute + n = getChar(s, tzIndex + 4, 10); + n += getChar(s, tzIndex + 5, 1); + n *= offset; + minute += n; + } + } + this.hour = hour; + this.minute = minute; + + if (offset != 0) { + normalize(false); + } + } else { + allDay = true; + this.hour = 0; + this.minute = 0; + this.second = 0; + } + + this.weekDay = 0; + this.yearDay = 0; + this.isDst = -1; + this.gmtoff = 0; + return inUtc; + } /** * Returns the timezone string that is currently set for the device. @@ -480,7 +677,9 @@ public class Time { /** * Sets the time of the given Time object to the current time. */ - native public void setToNow(); + public void setToNow() { + set(System.currentTimeMillis()); + } /** * Converts this time to milliseconds. Suitable for interacting with the @@ -530,7 +729,10 @@ public class Time { * to read back the same milliseconds that you set with {@link #set(long)} * or {@link #set(Time)} or after parsing a date string. */ - native public long toMillis(boolean ignoreDst); + public long toMillis(boolean ignoreDst) { + calculator.copyFieldsFromTime(this); + return calculator.toMillis(ignoreDst); + } /** * Sets the fields in this Time object given the UTC milliseconds. After @@ -539,15 +741,23 @@ public class Time { * * @param millis the time in UTC milliseconds since the epoch. */ - native public void set(long millis); + public void set(long millis) { + allDay = false; + calculator.timezone = timezone; + calculator.setTimeInMillis(millis); + calculator.copyFieldsToTime(this); + } /** - * Format according to RFC 2445 DATETIME type. + * Format according to RFC 2445 DATE-TIME type. * - * <p> - * The same as format("%Y%m%dT%H%M%S"). + * <p>The same as format("%Y%m%dT%H%M%S"), or format("%Y%m%dT%H%M%SZ") for a Time with a + * timezone set to "UTC". */ - native public String format2445(); + public String format2445() { + calculator.copyFieldsFromTime(this); + return calculator.format2445(!allDay); + } /** * Copy the value of that to this Time object. No normalization happens. @@ -682,7 +892,6 @@ public class Time { * Otherwise, if the timezone is UTC, expresses the time as Y-M-D-T-H-M-S UTC</p> * <p> * Otherwise the time is expressed the time as Y-M-D-T-H-M-S +- GMT</p> - * @param allDay * @return string in the RFC 3339 format. */ public String format3339(boolean allDay) { @@ -693,7 +902,7 @@ public class Time { } else { String base = format(Y_M_D_T_H_M_S_000); String sign = (gmtoff < 0) ? "-" : "+"; - int offset = (int)Math.abs(gmtoff); + int offset = (int) Math.abs(gmtoff); int minutes = (offset % 3600) / 60; int hours = offset / 3600; @@ -714,16 +923,18 @@ public class Time { } /** - * Computes the Julian day number, given the UTC milliseconds - * and the offset (in seconds) from UTC. The Julian day for a given - * date will be the same for every timezone. For example, the Julian - * day for July 1, 2008 is 2454649. This is the same value no matter - * what timezone is being used. The Julian day is useful for testing - * if two events occur on the same day and for determining the relative - * time of an event from the present ("yesterday", "3 days ago", etc.). + * Computes the Julian day number for a point in time in a particular + * timezone. The Julian day for a given date is the same for every + * timezone. For example, the Julian day for July 1, 2008 is 2454649. * - * <p> - * Use {@link #toMillis(boolean)} to get the milliseconds. + * <p>Callers must pass the time in UTC millisecond (as can be returned + * by {@link #toMillis(boolean)} or {@link #normalize(boolean)}) + * and the offset from UTC of the timezone in seconds (as might be in + * {@link #gmtoff}). + * + * <p>The Julian day is useful for testing if two events occur on the + * same calendar date and for determining the relative time of an event + * from the present ("yesterday", "3 days ago", etc.). * * @param millis the time in UTC milliseconds * @param gmtoff the offset from UTC in seconds @@ -810,4 +1021,240 @@ public class Time { public static int getJulianMondayFromWeeksSinceEpoch(int week) { return MONDAY_BEFORE_JULIAN_EPOCH + week * 7; } + + /** + * A class that handles date/time calculations. + * + * This class originated as a port of a native C++ class ("android.Time") to pure Java. It is + * separate from the enclosing class because some methods copy the result of calculations back + * to the enclosing object, but others do not: thus separate state is retained. + */ + private static class TimeCalculator { + public final ZoneInfo.WallTime wallTime; + public String timezone; + + // Information about the current timezone. + private ZoneInfo zoneInfo; + + public TimeCalculator(String timezoneId) { + this.zoneInfo = lookupZoneInfo(timezoneId); + this.wallTime = new ZoneInfo.WallTime(); + } + + public long toMillis(boolean ignoreDst) { + if (ignoreDst) { + wallTime.setIsDst(-1); + } + + int r = wallTime.mktime(zoneInfo); + if (r == -1) { + return -1; + } + return r * 1000L; + } + + public void setTimeInMillis(long millis) { + // Preserve old 32-bit Android behavior. + int intSeconds = (int) (millis / 1000); + + updateZoneInfoFromTimeZone(); + wallTime.localtime(intSeconds, zoneInfo); + } + + public String format(String format) { + if (format == null) { + format = "%c"; + } + TimeFormatter formatter = new TimeFormatter(); + return formatter.format(format, wallTime, zoneInfo); + } + + private void updateZoneInfoFromTimeZone() { + if (!zoneInfo.getID().equals(timezone)) { + this.zoneInfo = lookupZoneInfo(timezone); + } + } + + private static ZoneInfo lookupZoneInfo(String timezoneId) { + try { + ZoneInfo zoneInfo = ZoneInfoDB.getInstance().makeTimeZone(timezoneId); + if (zoneInfo == null) { + zoneInfo = ZoneInfoDB.getInstance().makeTimeZone("GMT"); + } + if (zoneInfo == null) { + throw new AssertionError("GMT not found: \"" + timezoneId + "\""); + } + return zoneInfo; + } catch (IOException e) { + // This should not ever be thrown. + throw new AssertionError("Error loading timezone: \"" + timezoneId + "\"", e); + } + } + + public void switchTimeZone(String timezone) { + int seconds = wallTime.mktime(zoneInfo); + this.timezone = timezone; + updateZoneInfoFromTimeZone(); + wallTime.localtime(seconds, zoneInfo); + } + + public String format2445(boolean hasTime) { + char[] buf = new char[hasTime ? 16 : 8]; + int n = wallTime.getYear(); + + buf[0] = toChar(n / 1000); + n %= 1000; + buf[1] = toChar(n / 100); + n %= 100; + buf[2] = toChar(n / 10); + n %= 10; + buf[3] = toChar(n); + + n = wallTime.getMonth() + 1; + buf[4] = toChar(n / 10); + buf[5] = toChar(n % 10); + + n = wallTime.getMonthDay(); + buf[6] = toChar(n / 10); + buf[7] = toChar(n % 10); + + if (!hasTime) { + return new String(buf, 0, 8); + } + + buf[8] = 'T'; + + n = wallTime.getHour(); + buf[9] = toChar(n / 10); + buf[10] = toChar(n % 10); + + n = wallTime.getMinute(); + buf[11] = toChar(n / 10); + buf[12] = toChar(n % 10); + + n = wallTime.getSecond(); + buf[13] = toChar(n / 10); + buf[14] = toChar(n % 10); + + if (TIMEZONE_UTC.equals(timezone)) { + // The letter 'Z' is appended to the end. + buf[15] = 'Z'; + return new String(buf, 0, 16); + } else { + return new String(buf, 0, 15); + } + } + + private char toChar(int n) { + return (n >= 0 && n <= 9) ? (char) (n + '0') : ' '; + } + + /** + * A method that will return the state of this object in string form. Note: it has side + * effects and so has deliberately not been made the default {@link #toString()}. + */ + public String toStringInternal() { + // This implementation possibly displays the un-normalized fields because that is + // what it has always done. + return String.format("%04d%02d%02dT%02d%02d%02d%s(%d,%d,%d,%d,%d)", + wallTime.getYear(), + wallTime.getMonth() + 1, + wallTime.getMonthDay(), + wallTime.getHour(), + wallTime.getMinute(), + wallTime.getSecond(), + timezone, + wallTime.getWeekDay(), + wallTime.getYearDay(), + wallTime.getGmtOffset(), + wallTime.getIsDst(), + toMillis(false /* use isDst */) / 1000 + ); + + } + + public static int compare(TimeCalculator aObject, TimeCalculator bObject) { + if (aObject.timezone.equals(bObject.timezone)) { + // If the timezones are the same, we can easily compare the two times. + int diff = aObject.wallTime.getYear() - bObject.wallTime.getYear(); + if (diff != 0) { + return diff; + } + + diff = aObject.wallTime.getMonth() - bObject.wallTime.getMonth(); + if (diff != 0) { + return diff; + } + + diff = aObject.wallTime.getMonthDay() - bObject.wallTime.getMonthDay(); + if (diff != 0) { + return diff; + } + + diff = aObject.wallTime.getHour() - bObject.wallTime.getHour(); + if (diff != 0) { + return diff; + } + + diff = aObject.wallTime.getMinute() - bObject.wallTime.getMinute(); + if (diff != 0) { + return diff; + } + + diff = aObject.wallTime.getSecond() - bObject.wallTime.getSecond(); + if (diff != 0) { + return diff; + } + + return 0; + } else { + // Otherwise, convert to milliseconds and compare that. This requires that object be + // normalized. Note: For dates that do not exist: toMillis() can return -1, which + // can be confused with a valid time. + long am = aObject.toMillis(false /* use isDst */); + long bm = bObject.toMillis(false /* use isDst */); + long diff = am - bm; + return (diff < 0) ? -1 : ((diff > 0) ? 1 : 0); + } + + } + + public void copyFieldsToTime(Time time) { + time.second = wallTime.getSecond(); + time.minute = wallTime.getMinute(); + time.hour = wallTime.getHour(); + time.monthDay = wallTime.getMonthDay(); + time.month = wallTime.getMonth(); + time.year = wallTime.getYear(); + + // Read-only fields that are derived from other information above. + time.weekDay = wallTime.getWeekDay(); + time.yearDay = wallTime.getYearDay(); + + // < 0: DST status unknown, 0: is not in DST, 1: is in DST + time.isDst = wallTime.getIsDst(); + // This is in seconds and includes any DST offset too. + time.gmtoff = wallTime.getGmtOffset(); + } + + public void copyFieldsFromTime(Time time) { + wallTime.setSecond(time.second); + wallTime.setMinute(time.minute); + wallTime.setHour(time.hour); + wallTime.setMonthDay(time.monthDay); + wallTime.setMonth(time.month); + wallTime.setYear(time.year); + wallTime.setWeekDay(time.weekDay); + wallTime.setYearDay(time.yearDay); + wallTime.setIsDst(time.isDst); + wallTime.setGmtOffset((int) time.gmtoff); + + if (time.allDay && (time.second != 0 || time.minute != 0 || time.hour != 0)) { + throw new IllegalArgumentException("allDay is true but sec, min, hour are not 0."); + } + + timezone = time.timezone; + updateZoneInfoFromTimeZone(); + } + } } diff --git a/core/java/android/text/format/TimeFormatter.java b/core/java/android/text/format/TimeFormatter.java new file mode 100644 index 0000000..ec79b36 --- /dev/null +++ b/core/java/android/text/format/TimeFormatter.java @@ -0,0 +1,519 @@ +/* + * Based on the UCB version of strftime.c with the copyright notice appearing below. + */ + +/* +** Copyright (c) 1989 The Regents of the University of California. +** All rights reserved. +** +** Redistribution and use in source and binary forms are permitted +** provided that the above copyright notice and this paragraph are +** duplicated in all such forms and that any documentation, +** advertising materials, and other materials related to such +** distribution and use acknowledge that the software was developed +** by the University of California, Berkeley. The name of the +** University may not be used to endorse or promote products derived +** from this software without specific prior written permission. +** THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED +** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +*/ +package android.text.format; + +import android.content.res.Resources; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Formatter; +import java.util.Locale; +import java.util.TimeZone; +import libcore.icu.LocaleData; +import libcore.util.ZoneInfo; + +/** + * Formatting logic for {@link Time}. Contains a port of Bionic's broken strftime_tz to Java. The + * main issue with this implementation is the treatment of characters as ASCII, despite returning + * localized (UTF-16) strings from the LocaleData. + * + * <p>This class is not thread safe. + */ +class TimeFormatter { + // An arbitrary value outside the range representable by a byte / ASCII character code. + private static final int FORCE_LOWER_CASE = 0x100; + + private static final int SECSPERMIN = 60; + private static final int MINSPERHOUR = 60; + private static final int DAYSPERWEEK = 7; + private static final int MONSPERYEAR = 12; + private static final int HOURSPERDAY = 24; + private static final int DAYSPERLYEAR = 366; + private static final int DAYSPERNYEAR = 365; + + /** + * The Locale for which the cached LocaleData and formats have been loaded. + */ + private static Locale sLocale; + private static LocaleData sLocaleData; + private static String sTimeOnlyFormat; + private static String sDateOnlyFormat; + private static String sDateTimeFormat; + + private final LocaleData localeData; + private final String dateTimeFormat; + private final String timeOnlyFormat; + private final String dateOnlyFormat; + private final Locale locale; + + private StringBuilder outputBuilder; + private Formatter outputFormatter; + + public TimeFormatter() { + synchronized (TimeFormatter.class) { + Locale locale = Locale.getDefault(); + + if (sLocale == null || !(locale.equals(sLocale))) { + sLocale = locale; + sLocaleData = LocaleData.get(locale); + + Resources r = Resources.getSystem(); + sTimeOnlyFormat = r.getString(com.android.internal.R.string.time_of_day); + sDateOnlyFormat = r.getString(com.android.internal.R.string.month_day_year); + sDateTimeFormat = r.getString(com.android.internal.R.string.date_and_time); + } + + this.dateTimeFormat = sDateTimeFormat; + this.timeOnlyFormat = sTimeOnlyFormat; + this.dateOnlyFormat = sDateOnlyFormat; + this.locale = locale; + localeData = sLocaleData; + } + } + + /** + * Format the specified {@code wallTime} using {@code pattern}. The output is returned. + */ + public String format(String pattern, ZoneInfo.WallTime wallTime, ZoneInfo zoneInfo) { + try { + StringBuilder stringBuilder = new StringBuilder(); + + outputBuilder = stringBuilder; + outputFormatter = new Formatter(stringBuilder, locale); + + formatInternal(pattern, wallTime, zoneInfo); + String result = stringBuilder.toString(); + // This behavior is the source of a bug since some formats are defined as being + // in ASCII. Generally localization is very broken. + if (localeData.zeroDigit != '0') { + result = localizeDigits(result); + } + return result; + } finally { + outputBuilder = null; + outputFormatter = null; + } + } + + private String localizeDigits(String s) { + int length = s.length(); + int offsetToLocalizedDigits = localeData.zeroDigit - '0'; + StringBuilder result = new StringBuilder(length); + for (int i = 0; i < length; ++i) { + char ch = s.charAt(i); + if (ch >= '0' && ch <= '9') { + ch += offsetToLocalizedDigits; + } + result.append(ch); + } + return result.toString(); + } + + /** + * Format the specified {@code wallTime} using {@code pattern}. The output is written to + * {@link #outputBuilder}. + */ + private void formatInternal(String pattern, ZoneInfo.WallTime wallTime, ZoneInfo zoneInfo) { + // Convert to ASCII bytes to be compatible with old implementation behavior. + byte[] bytes = pattern.getBytes(StandardCharsets.US_ASCII); + if (bytes.length == 0) { + return; + } + + ByteBuffer formatBuffer = ByteBuffer.wrap(bytes); + while (formatBuffer.remaining() > 0) { + boolean outputCurrentByte = true; + char currentByteAsChar = convertToChar(formatBuffer.get(formatBuffer.position())); + if (currentByteAsChar == '%') { + outputCurrentByte = handleToken(formatBuffer, wallTime, zoneInfo); + } + if (outputCurrentByte) { + currentByteAsChar = convertToChar(formatBuffer.get(formatBuffer.position())); + outputBuilder.append(currentByteAsChar); + } + + formatBuffer.position(formatBuffer.position() + 1); + } + } + + private boolean handleToken(ByteBuffer formatBuffer, ZoneInfo.WallTime wallTime, + ZoneInfo zoneInfo) { + + // The byte at formatBuffer.position() is expected to be '%' at this point. + int modifier = 0; + while (formatBuffer.remaining() > 1) { + // Increment the position then get the new current byte. + formatBuffer.position(formatBuffer.position() + 1); + char currentByteAsChar = convertToChar(formatBuffer.get(formatBuffer.position())); + switch (currentByteAsChar) { + case 'A': + modifyAndAppend((wallTime.getWeekDay() < 0 + || wallTime.getWeekDay() >= DAYSPERWEEK) + ? "?" : localeData.longWeekdayNames[wallTime.getWeekDay() + 1], + modifier); + return false; + case 'a': + modifyAndAppend((wallTime.getWeekDay() < 0 + || wallTime.getWeekDay() >= DAYSPERWEEK) + ? "?" : localeData.shortWeekdayNames[wallTime.getWeekDay() + 1], + modifier); + return false; + case 'B': + if (modifier == '-') { + modifyAndAppend((wallTime.getMonth() < 0 + || wallTime.getMonth() >= MONSPERYEAR) + ? "?" + : localeData.longStandAloneMonthNames[wallTime.getMonth()], + modifier); + } else { + modifyAndAppend((wallTime.getMonth() < 0 + || wallTime.getMonth() >= MONSPERYEAR) + ? "?" : localeData.longMonthNames[wallTime.getMonth()], + modifier); + } + return false; + case 'b': + case 'h': + modifyAndAppend((wallTime.getMonth() < 0 || wallTime.getMonth() >= MONSPERYEAR) + ? "?" : localeData.shortMonthNames[wallTime.getMonth()], + modifier); + return false; + case 'C': + outputYear(wallTime.getYear(), true, false, modifier); + return false; + case 'c': + formatInternal(dateTimeFormat, wallTime, zoneInfo); + return false; + case 'D': + formatInternal("%m/%d/%y", wallTime, zoneInfo); + return false; + case 'd': + outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), + wallTime.getMonthDay()); + return false; + case 'E': + case 'O': + // C99 locale modifiers are not supported. + continue; + case '_': + case '-': + case '0': + case '^': + case '#': + modifier = currentByteAsChar; + continue; + case 'e': + outputFormatter.format(getFormat(modifier, "%2d", "%2d", "%d", "%02d"), + wallTime.getMonthDay()); + return false; + case 'F': + formatInternal("%Y-%m-%d", wallTime, zoneInfo); + return false; + case 'H': + outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), + wallTime.getHour()); + return false; + case 'I': + int hour = (wallTime.getHour() % 12 != 0) ? (wallTime.getHour() % 12) : 12; + outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), hour); + return false; + case 'j': + int yearDay = wallTime.getYearDay() + 1; + outputFormatter.format(getFormat(modifier, "%03d", "%3d", "%d", "%03d"), + yearDay); + return false; + case 'k': + outputFormatter.format(getFormat(modifier, "%2d", "%2d", "%d", "%02d"), + wallTime.getHour()); + return false; + case 'l': + int n2 = (wallTime.getHour() % 12 != 0) ? (wallTime.getHour() % 12) : 12; + outputFormatter.format(getFormat(modifier, "%2d", "%2d", "%d", "%02d"), n2); + return false; + case 'M': + outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), + wallTime.getMinute()); + return false; + case 'm': + outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), + wallTime.getMonth() + 1); + return false; + case 'n': + modifyAndAppend("\n", modifier); + return false; + case 'p': + modifyAndAppend((wallTime.getHour() >= (HOURSPERDAY / 2)) ? localeData.amPm[1] + : localeData.amPm[0], modifier); + return false; + case 'P': + modifyAndAppend((wallTime.getHour() >= (HOURSPERDAY / 2)) ? localeData.amPm[1] + : localeData.amPm[0], FORCE_LOWER_CASE); + return false; + case 'R': + formatInternal("%H:%M", wallTime, zoneInfo); + return false; + case 'r': + formatInternal("%I:%M:%S %p", wallTime, zoneInfo); + return false; + case 'S': + outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), + wallTime.getSecond()); + return false; + case 's': + int timeInSeconds = wallTime.mktime(zoneInfo); + modifyAndAppend(Integer.toString(timeInSeconds), modifier); + return false; + case 'T': + formatInternal("%H:%M:%S", wallTime, zoneInfo); + return false; + case 't': + modifyAndAppend("\t", modifier); + return false; + case 'U': + outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), + (wallTime.getYearDay() + DAYSPERWEEK - wallTime.getWeekDay()) + / DAYSPERWEEK); + return false; + case 'u': + int day = (wallTime.getWeekDay() == 0) ? DAYSPERWEEK : wallTime.getWeekDay(); + outputFormatter.format("%d", day); + return false; + case 'V': /* ISO 8601 week number */ + case 'G': /* ISO 8601 year (four digits) */ + case 'g': /* ISO 8601 year (two digits) */ + { + int year = wallTime.getYear(); + int yday = wallTime.getYearDay(); + int wday = wallTime.getWeekDay(); + int w; + while (true) { + int len = isLeap(year) ? DAYSPERLYEAR : DAYSPERNYEAR; + // What yday (-3 ... 3) does the ISO year begin on? + int bot = ((yday + 11 - wday) % DAYSPERWEEK) - 3; + // What yday does the NEXT ISO year begin on? + int top = bot - (len % DAYSPERWEEK); + if (top < -3) { + top += DAYSPERWEEK; + } + top += len; + if (yday >= top) { + ++year; + w = 1; + break; + } + if (yday >= bot) { + w = 1 + ((yday - bot) / DAYSPERWEEK); + break; + } + --year; + yday += isLeap(year) ? DAYSPERLYEAR : DAYSPERNYEAR; + } + if (currentByteAsChar == 'V') { + outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), w); + } else if (currentByteAsChar == 'g') { + outputYear(year, false, true, modifier); + } else { + outputYear(year, true, true, modifier); + } + return false; + } + case 'v': + formatInternal("%e-%b-%Y", wallTime, zoneInfo); + return false; + case 'W': + int n = (wallTime.getYearDay() + DAYSPERWEEK - ( + wallTime.getWeekDay() != 0 ? (wallTime.getWeekDay() - 1) + : (DAYSPERWEEK - 1))) / DAYSPERWEEK; + outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), n); + return false; + case 'w': + outputFormatter.format("%d", wallTime.getWeekDay()); + return false; + case 'X': + formatInternal(timeOnlyFormat, wallTime, zoneInfo); + return false; + case 'x': + formatInternal(dateOnlyFormat, wallTime, zoneInfo); + return false; + case 'y': + outputYear(wallTime.getYear(), false, true, modifier); + return false; + case 'Y': + outputYear(wallTime.getYear(), true, true, modifier); + return false; + case 'Z': + if (wallTime.getIsDst() < 0) { + return false; + } + boolean isDst = wallTime.getIsDst() != 0; + modifyAndAppend(zoneInfo.getDisplayName(isDst, TimeZone.SHORT), modifier); + return false; + case 'z': { + if (wallTime.getIsDst() < 0) { + return false; + } + int diff = wallTime.getGmtOffset(); + String sign; + if (diff < 0) { + sign = "-"; + diff = -diff; + } else { + sign = "+"; + } + modifyAndAppend(sign, modifier); + diff /= SECSPERMIN; + diff = (diff / MINSPERHOUR) * 100 + (diff % MINSPERHOUR); + outputFormatter.format(getFormat(modifier, "%04d", "%4d", "%d", "%04d"), diff); + return false; + } + case '+': + formatInternal("%a %b %e %H:%M:%S %Z %Y", wallTime, zoneInfo); + return false; + case '%': + // If conversion char is undefined, behavior is undefined. Print out the + // character itself. + default: + return true; + } + } + return true; + } + + private void modifyAndAppend(CharSequence str, int modifier) { + switch (modifier) { + case FORCE_LOWER_CASE: + for (int i = 0; i < str.length(); i++) { + outputBuilder.append(brokenToLower(str.charAt(i))); + } + break; + case '^': + for (int i = 0; i < str.length(); i++) { + outputBuilder.append(brokenToUpper(str.charAt(i))); + } + break; + case '#': + for (int i = 0; i < str.length(); i++) { + char c = str.charAt(i); + if (brokenIsUpper(c)) { + c = brokenToLower(c); + } else if (brokenIsLower(c)) { + c = brokenToUpper(c); + } + outputBuilder.append(c); + } + break; + default: + outputBuilder.append(str); + + } + } + + private void outputYear(int value, boolean outputTop, boolean outputBottom, int modifier) { + int lead; + int trail; + + final int DIVISOR = 100; + trail = value % DIVISOR; + lead = value / DIVISOR + trail / DIVISOR; + trail %= DIVISOR; + if (trail < 0 && lead > 0) { + trail += DIVISOR; + --lead; + } else if (lead < 0 && trail > 0) { + trail -= DIVISOR; + ++lead; + } + if (outputTop) { + if (lead == 0 && trail < 0) { + modifyAndAppend("-0", modifier); + } else { + outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), lead); + } + } + if (outputBottom) { + int n = ((trail < 0) ? -trail : trail); + outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), n); + } + } + + private static String getFormat(int modifier, String normal, String underscore, String dash, + String zero) { + switch (modifier) { + case '_': + return underscore; + case '-': + return dash; + case '0': + return zero; + } + return normal; + } + + private static boolean isLeap(int year) { + return (((year) % 4) == 0 && (((year) % 100) != 0 || ((year) % 400) == 0)); + } + + /** + * A broken implementation of {@link Character#isUpperCase(char)} that assumes ASCII in order to + * be compatible with the old native implementation. + */ + private static boolean brokenIsUpper(char toCheck) { + return toCheck >= 'A' && toCheck <= 'Z'; + } + + /** + * A broken implementation of {@link Character#isLowerCase(char)} that assumes ASCII in order to + * be compatible with the old native implementation. + */ + private static boolean brokenIsLower(char toCheck) { + return toCheck >= 'a' && toCheck <= 'z'; + } + + /** + * A broken implementation of {@link Character#toLowerCase(char)} that assumes ASCII in order to + * be compatible with the old native implementation. + */ + private static char brokenToLower(char input) { + if (input >= 'A' && input <= 'Z') { + return (char) (input - 'A' + 'a'); + } + return input; + } + + /** + * A broken implementation of {@link Character#toUpperCase(char)} that assumes ASCII in order to + * be compatible with the old native implementation. + */ + private static char brokenToUpper(char input) { + if (input >= 'a' && input <= 'z') { + return (char) (input - 'a' + 'A'); + } + return input; + } + + /** + * Safely convert a byte containing an ASCII character to a char, even for character codes + * > 127. + */ + private static char convertToChar(byte b) { + return (char) (b & 0xFF); + } +} diff --git a/core/java/android/util/TimeFormatException.java b/core/java/android/util/TimeFormatException.java index d7a898b..f520523 100644 --- a/core/java/android/util/TimeFormatException.java +++ b/core/java/android/util/TimeFormatException.java @@ -18,7 +18,11 @@ package android.util; public class TimeFormatException extends RuntimeException { - TimeFormatException(String s) + + /** + * @hide + */ + public TimeFormatException(String s) { super(s); } diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java index 67a94be..2a8523f 100644 --- a/core/java/android/view/ViewPropertyAnimator.java +++ b/core/java/android/view/ViewPropertyAnimator.java @@ -314,8 +314,8 @@ public class ViewPropertyAnimator { */ public ViewPropertyAnimator setStartDelay(long startDelay) { if (startDelay < 0) { - throw new IllegalArgumentException("Animators cannot have negative duration: " + - startDelay); + throw new IllegalArgumentException("Animators cannot have negative start " + + "delay: " + startDelay); } mStartDelaySet = true; mStartDelay = startDelay; diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java index 26c5732..7f6823d 100644 --- a/core/java/android/widget/NumberPicker.java +++ b/core/java/android/widget/NumberPicker.java @@ -59,7 +59,7 @@ import java.util.List; import java.util.Locale; /** - * A widget that enables the user to select a number form a predefined range. + * A widget that enables the user to select a number from a predefined range. * There are two flavors of this widget and which one is presented to the user * depends on the current theme. * <ul> diff --git a/core/java/com/android/internal/app/LocalePicker.java b/core/java/com/android/internal/app/LocalePicker.java index 043964f..229df8f 100644 --- a/core/java/com/android/internal/app/LocalePicker.java +++ b/core/java/com/android/internal/app/LocalePicker.java @@ -36,7 +36,8 @@ import android.widget.ListView; import android.widget.TextView; import java.text.Collator; -import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Locale; import java.util.ArrayList; @@ -104,93 +105,84 @@ public class LocalePicker extends ListFragment { return constructAdapter(context, layoutId, fieldId, false /* disable pseudolocales */); } - public static ArrayAdapter<LocaleInfo> constructAdapter(Context context, - final int layoutId, final int fieldId, final boolean isInDeveloperMode) { + public static List<LocaleInfo> getAllAssetLocales(Context context, boolean isInDeveloperMode) { final Resources resources = context.getResources(); - ArrayList<String> localeList = new ArrayList<String>(Arrays.asList( - Resources.getSystem().getAssets().getLocales())); + final String[] locales = Resources.getSystem().getAssets().getLocales(); + List<String> localeList = new ArrayList<String>(locales.length); + Collections.addAll(localeList, locales); if (isInDeveloperMode) { if (!localeList.contains("zz_ZZ")) { localeList.add("zz_ZZ"); } - /** - TODO: Enable when zz_ZY Pseudolocale is complete - * if (!localeList.contains("zz_ZY")) { - * localeList.add("zz_ZY"); - * } - */ + /** - TODO: Enable when zz_ZY Pseudolocale is complete + * if (!localeList.contains("zz_ZY")) { + * localeList.add("zz_ZY"); + * } + */ } - String[] locales = new String[localeList.size()]; - locales = localeList.toArray(locales); + Collections.sort(localeList); final String[] specialLocaleCodes = resources.getStringArray(R.array.special_locale_codes); final String[] specialLocaleNames = resources.getStringArray(R.array.special_locale_names); - Arrays.sort(locales); - final int origSize = locales.length; - final LocaleInfo[] preprocess = new LocaleInfo[origSize]; - int finalSize = 0; - for (int i = 0 ; i < origSize; i++ ) { - final String s = locales[i]; - final int len = s.length(); - if (len == 5) { - String language = s.substring(0, 2); - String country = s.substring(3, 5); - final Locale l = new Locale(language, country); - - if (finalSize == 0) { + + final ArrayList<LocaleInfo> localeInfos = new ArrayList<LocaleInfo>(localeList.size()); + for (String locale : localeList) { + final Locale l = Locale.forLanguageTag(locale.replace('_', '-')); + if (l == null || "und".equals(l.getLanguage()) + || l.getLanguage().isEmpty() || l.getCountry().isEmpty()) { + continue; + } + + if (localeInfos.isEmpty()) { + if (DEBUG) { + Log.v(TAG, "adding initial "+ toTitleCase(l.getDisplayLanguage(l))); + } + localeInfos.add(new LocaleInfo(toTitleCase(l.getDisplayLanguage(l)), l)); + } else { + // check previous entry: + // same lang and a country -> upgrade to full name and + // insert ours with full name + // diff lang -> insert ours with lang-only name + final LocaleInfo previous = localeInfos.get(localeInfos.size() - 1); + if (previous.locale.getLanguage().equals(l.getLanguage()) && + !previous.locale.getLanguage().equals("zz")) { + if (DEBUG) { + Log.v(TAG, "backing up and fixing " + previous.label + " to " + + getDisplayName(previous.locale, specialLocaleCodes, specialLocaleNames)); + } + previous.label = toTitleCase(getDisplayName( + previous.locale, specialLocaleCodes, specialLocaleNames)); if (DEBUG) { - Log.v(TAG, "adding initial "+ toTitleCase(l.getDisplayLanguage(l))); + Log.v(TAG, " and adding "+ toTitleCase( + getDisplayName(l, specialLocaleCodes, specialLocaleNames))); } - preprocess[finalSize++] = - new LocaleInfo(toTitleCase(l.getDisplayLanguage(l)), l); + localeInfos.add(new LocaleInfo(toTitleCase( + getDisplayName(l, specialLocaleCodes, specialLocaleNames)), l)); } else { - // check previous entry: - // same lang and a country -> upgrade to full name and - // insert ours with full name - // diff lang -> insert ours with lang-only name - if (preprocess[finalSize-1].locale.getLanguage().equals( - language) && - !preprocess[finalSize-1].locale.getLanguage().equals("zz")) { - if (DEBUG) { - Log.v(TAG, "backing up and fixing "+ - preprocess[finalSize-1].label+" to "+ - getDisplayName(preprocess[finalSize-1].locale, - specialLocaleCodes, specialLocaleNames)); - } - preprocess[finalSize-1].label = toTitleCase( - getDisplayName(preprocess[finalSize-1].locale, - specialLocaleCodes, specialLocaleNames)); - if (DEBUG) { - Log.v(TAG, " and adding "+ toTitleCase( - getDisplayName(l, specialLocaleCodes, specialLocaleNames))); - } - preprocess[finalSize++] = - new LocaleInfo(toTitleCase( - getDisplayName( - l, specialLocaleCodes, specialLocaleNames)), l); + String displayName; + if (locale.equals("zz_ZZ")) { + displayName = "[Developer] Accented English"; + } else if (locale.equals("zz_ZY")) { + displayName = "[Developer] Fake Bi-Directional"; } else { - String displayName; - if (s.equals("zz_ZZ")) { - displayName = "[Developer] Accented English"; - } else if (s.equals("zz_ZY")) { - displayName = "[Developer] Fake Bi-Directional"; - } else { - displayName = toTitleCase(l.getDisplayLanguage(l)); - } - if (DEBUG) { - Log.v(TAG, "adding "+displayName); - } - preprocess[finalSize++] = new LocaleInfo(displayName, l); + displayName = toTitleCase(l.getDisplayLanguage(l)); } + if (DEBUG) { + Log.v(TAG, "adding "+displayName); + } + localeInfos.add(new LocaleInfo(displayName, l)); } } } - final LocaleInfo[] localeInfos = new LocaleInfo[finalSize]; - for (int i = 0; i < finalSize; i++) { - localeInfos[i] = preprocess[i]; - } - Arrays.sort(localeInfos); + Collections.sort(localeInfos); + return localeInfos; + } + + public static ArrayAdapter<LocaleInfo> constructAdapter(Context context, + final int layoutId, final int fieldId, final boolean isInDeveloperMode) { + final List<LocaleInfo> localeInfos = getAllAssetLocales(context, isInDeveloperMode); final LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java index 494bc78..26433d2 100644 --- a/core/java/com/android/internal/backup/LocalTransport.java +++ b/core/java/com/android/internal/backup/LocalTransport.java @@ -47,7 +47,7 @@ import java.util.ArrayList; public class LocalTransport extends IBackupTransport.Stub { private static final String TAG = "LocalTransport"; - private static final boolean DEBUG = true; + private static final boolean DEBUG = false; private static final String TRANSPORT_DIR_NAME = "com.android.internal.backup.LocalTransport"; diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java index dab3aff..832829d 100644 --- a/core/java/com/android/internal/content/NativeLibraryHelper.java +++ b/core/java/com/android/internal/content/NativeLibraryHelper.java @@ -19,6 +19,7 @@ package com.android.internal.content; import android.content.pm.PackageManager; import android.util.Slog; +import java.io.Closeable; import java.io.File; import java.io.IOException; @@ -39,20 +40,29 @@ public class NativeLibraryHelper { * * @hide */ - public static class ApkHandle { + public static class ApkHandle implements Closeable { final String apkPath; final long apkHandle; - public ApkHandle(String path) { - apkPath = path; - apkHandle = nativeOpenApk(apkPath); + public static ApkHandle create(String path) throws IOException { + final long handle = nativeOpenApk(path); + if (handle == 0) { + throw new IOException("Unable to open APK: " + path); + } + + return new ApkHandle(path, handle); + } + + public static ApkHandle create(File path) throws IOException { + return create(path.getAbsolutePath()); } - public ApkHandle(File apkFile) { - apkPath = apkFile.getPath(); - apkHandle = nativeOpenApk(apkPath); + private ApkHandle(String apkPath, long apkHandle) { + this.apkPath = apkPath; + this.apkHandle = apkHandle; } + @Override public void close() { nativeClose(apkHandle); } diff --git a/core/java/com/android/internal/inputmethod/InputMethodUtils.java b/core/java/com/android/internal/inputmethod/InputMethodUtils.java index 63d018f..68260d2 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodUtils.java +++ b/core/java/com/android/internal/inputmethod/InputMethodUtils.java @@ -262,6 +262,7 @@ public class InputMethodUtils { final List<InputMethodSubtype> subtypes = InputMethodUtils.getSubtypes(imi); final String systemLocale = res.getConfiguration().locale.toString(); if (TextUtils.isEmpty(systemLocale)) return new ArrayList<InputMethodSubtype>(); + final String systemLanguage = res.getConfiguration().locale.getLanguage(); final HashMap<String, InputMethodSubtype> applicableModeAndSubtypesMap = new HashMap<String, InputMethodSubtype>(); final int N = subtypes.size(); @@ -282,15 +283,22 @@ public class InputMethodUtils { final InputMethodSubtype subtype = subtypes.get(i); final String locale = subtype.getLocale(); final String mode = subtype.getMode(); + final String language = getLanguageFromLocaleString(locale); // When system locale starts with subtype's locale, that subtype will be applicable - // for system locale + // for system locale. We need to make sure the languages are the same, to prevent + // locales like "fil" (Filipino) being matched by "fi" (Finnish). + // // For instance, it's clearly applicable for cases like system locale = en_US and // subtype = en, but it is not necessarily considered applicable for cases like system // locale = en and subtype = en_US. + // // We just call systemLocale.startsWith(locale) in this function because there is no // need to find applicable subtypes aggressively unlike // findLastResortApplicableSubtypeLocked. - if (systemLocale.startsWith(locale)) { + // + // TODO: This check is broken. It won't take scripts into account and doesn't + // account for the mandatory conversions performed by Locale#toString. + if (language.equals(systemLanguage) && systemLocale.startsWith(locale)) { final InputMethodSubtype applicableSubtype = applicableModeAndSubtypesMap.get(mode); // If more applicable subtypes are contained, skip. if (applicableSubtype != null) { @@ -335,6 +343,18 @@ public class InputMethodUtils { } /** + * Returns the language component of a given locale string. + */ + private static String getLanguageFromLocaleString(String locale) { + final int idx = locale.indexOf('_'); + if (idx < 0) { + return locale; + } else { + return locale.substring(0, idx); + } + } + + /** * If there are no selected subtypes, tries finding the most applicable one according to the * given locale. * @param subtypes this function will search the most applicable subtype in subtypes @@ -353,7 +373,7 @@ public class InputMethodUtils { if (TextUtils.isEmpty(locale)) { locale = res.getConfiguration().locale.toString(); } - final String language = locale.substring(0, 2); + final String language = getLanguageFromLocaleString(locale); boolean partialMatchFound = false; InputMethodSubtype applicableSubtype = null; InputMethodSubtype firstMatchedModeSubtype = null; @@ -361,6 +381,7 @@ public class InputMethodUtils { for (int i = 0; i < N; ++i) { InputMethodSubtype subtype = subtypes.get(i); final String subtypeLocale = subtype.getLocale(); + final String subtypeLanguage = getLanguageFromLocaleString(subtypeLocale); // An applicable subtype should match "mode". If mode is null, mode will be ignored, // and all subtypes with all modes can be candidates. if (mode == null || subtypes.get(i).getMode().equalsIgnoreCase(mode)) { @@ -371,7 +392,7 @@ public class InputMethodUtils { // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US") applicableSubtype = subtype; break; - } else if (!partialMatchFound && subtypeLocale.startsWith(language)) { + } else if (!partialMatchFound && language.equals(subtypeLanguage)) { // Partial match (e.g. system locale is "en_US" and subtype locale is "en") applicableSubtype = subtype; partialMatchFound = true; diff --git a/core/jni/Android.mk b/core/jni/Android.mk index bdef428..e4fad09 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -22,7 +22,6 @@ LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES LOCAL_SRC_FILES:= \ AndroidRuntime.cpp \ - Time.cpp \ com_android_internal_content_NativeLibraryHelper.cpp \ com_google_android_gles_jni_EGLImpl.cpp \ com_google_android_gles_jni_GLImpl.cpp.arm \ @@ -76,7 +75,6 @@ LOCAL_SRC_FILES:= \ android_net_TrafficStats.cpp \ android_net_wifi_WifiNative.cpp \ android_nio_utils.cpp \ - android_text_format_Time.cpp \ android_util_AssetManager.cpp \ android_util_Binder.cpp \ android_util_EventLog.cpp \ @@ -162,6 +160,7 @@ LOCAL_C_INCLUDES += \ $(call include-path-for, libhardware_legacy)/hardware_legacy \ $(TOP)/frameworks/av/include \ $(TOP)/system/media/camera/include \ + external/icu/icu4c/source/common \ external/skia/src/core \ external/skia/src/pdf \ external/skia/src/images \ @@ -171,8 +170,6 @@ LOCAL_C_INCLUDES += \ external/expat/lib \ external/openssl/include \ external/tremor/Tremor \ - external/icu4c/i18n \ - external/icu4c/common \ external/jpeg \ external/harfbuzz_ng/src \ external/zlib \ diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index a61901b..a65c351 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -132,7 +132,6 @@ extern int register_android_database_SQLiteConnection(JNIEnv* env); extern int register_android_database_SQLiteGlobal(JNIEnv* env); extern int register_android_database_SQLiteDebug(JNIEnv* env); extern int register_android_nio_utils(JNIEnv* env); -extern int register_android_text_format_Time(JNIEnv* env); extern int register_android_os_Debug(JNIEnv* env); extern int register_android_os_MessageQueue(JNIEnv* env); extern int register_android_os_Parcel(JNIEnv* env); @@ -391,8 +390,8 @@ static void readLocale(char* language, char* region) property_get("ro.product.locale.language", propLang, "en"); property_get("ro.product.locale.region", propRegn, "US"); } - strncat(language, propLang, 2); - strncat(region, propRegn, 2); + strncat(language, propLang, 3); + strncat(region, propRegn, 3); //ALOGD("language=%s region=%s\n", language, region); } @@ -436,6 +435,82 @@ void AndroidRuntime::parseExtraOpts(char* extraOptsBuf, const char* quotingArg) } /* + * Reads a "property" into "buffer" with a default of "defaultArg". If + * the property is non-empty, it is treated as a runtime option such + * as "-Xmx32m". + * + * The "runtimeArg" is a prefix for the option such as "-Xms" or "-Xmx". + * + * If an argument is found, it is added to mOptions. + * + * If an option is found, it is added to mOptions and true is + * returned. Otherwise false is returned. + */ +bool AndroidRuntime::parseRuntimeOption(const char* property, + char* buffer, + const char* runtimeArg, + const char* defaultArg) +{ + strcpy(buffer, runtimeArg); + size_t runtimeArgLen = strlen(runtimeArg); + property_get(property, buffer+runtimeArgLen, defaultArg); + if (buffer[runtimeArgLen] == '\0') { + return false; + } + + JavaVMOption opt; + memset(&opt, 0, sizeof(opt)); + + opt.optionString = buffer; + mOptions.add(opt); + + return true; +} + +/* + * Reads a "property" into "buffer". If the property is non-empty, it + * is treated as a dex2oat compiler runtime option that should be + * passed as a quoted option, e.g. "-Ximage-compiler-option + * --runtime-arg -Ximage-compiler-option -Xmx32m". + * + * The "runtimeArg" is a prefix for the option such as "-Xms" or "-Xmx". + * + * The "quotingArg" should be "-Ximage-compiler-option" or "-Xcompiler-option". + * + * If an option is found, it is added to mOptions and true is + * returned. Otherwise false is returned. + */ +bool AndroidRuntime::parseCompilerRuntimeOption(const char* property, + char* buffer, + const char* runtimeArg, + const char* quotingArg) +{ + strcpy(buffer, runtimeArg); + size_t runtimeArgLen = strlen(runtimeArg); + property_get(property, buffer+runtimeArgLen, ""); + if (buffer[runtimeArgLen] == '\0') { + return false; + } + + JavaVMOption opt; + memset(&opt, 0, sizeof(opt)); + + opt.optionString = quotingArg; + mOptions.add(opt); + + opt.optionString = "--runtime-arg"; + mOptions.add(opt); + + opt.optionString = quotingArg; + mOptions.add(opt); + + opt.optionString = buffer; + mOptions.add(opt); + + return true; +} + +/* * Start the Dalvik Virtual Machine. * * Various arguments, most determined by system properties, are passed in. @@ -457,7 +532,7 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) JavaVMInitArgs initArgs; JavaVMOption opt; char propBuf[PROPERTY_VALUE_MAX]; - char stackTraceFileBuf[PROPERTY_VALUE_MAX]; + char stackTraceFileBuf[sizeof("-Xstacktracefile:")-1 + PROPERTY_VALUE_MAX]; char dexoptFlagsBuf[PROPERTY_VALUE_MAX]; char enableAssertBuf[sizeof("-ea:")-1 + PROPERTY_VALUE_MAX]; char jniOptsBuf[sizeof("-Xjniopts:")-1 + PROPERTY_VALUE_MAX]; @@ -471,31 +546,35 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) char heaptargetutilizationOptsBuf[sizeof("-XX:HeapTargetUtilization=")-1 + PROPERTY_VALUE_MAX]; char jitcodecachesizeOptsBuf[sizeof("-Xjitcodecachesize:")-1 + PROPERTY_VALUE_MAX]; char dalvikVmLibBuf[PROPERTY_VALUE_MAX]; + char dex2oatXmsImageFlagsBuf[sizeof("-Xms")-1 + PROPERTY_VALUE_MAX]; + char dex2oatXmxImageFlagsBuf[sizeof("-Xmx")-1 + PROPERTY_VALUE_MAX]; + char dex2oatXmsFlagsBuf[sizeof("-Xms")-1 + PROPERTY_VALUE_MAX]; + char dex2oatXmxFlagsBuf[sizeof("-Xmx")-1 + PROPERTY_VALUE_MAX]; char dex2oatFlagsBuf[PROPERTY_VALUE_MAX]; char dex2oatImageFlagsBuf[PROPERTY_VALUE_MAX]; char extraOptsBuf[PROPERTY_VALUE_MAX]; - char* stackTraceFile = NULL; - bool checkJni = false; - bool checkDexSum = false; - bool logStdio = false; enum { kEMDefault, kEMIntPortable, kEMIntFast, kEMJitCompiler, } executionMode = kEMDefault; - char profile_period[sizeof("-Xprofile-period:") + PROPERTY_VALUE_MAX]; - char profile_duration[sizeof("-Xprofile-duration:") + PROPERTY_VALUE_MAX]; - char profile_interval[sizeof("-Xprofile-interval:") + PROPERTY_VALUE_MAX]; - char profile_backoff[sizeof("-Xprofile-backoff:") + PROPERTY_VALUE_MAX]; - char profile_top_k_threshold[sizeof("-Xprofile-top-k-threshold:") + PROPERTY_VALUE_MAX]; - char profile_top_k_change_threshold[sizeof("-Xprofile-top-k-change-threshold:") + PROPERTY_VALUE_MAX]; + char profilePeriod[sizeof("-Xprofile-period:")-1 + PROPERTY_VALUE_MAX]; + char profileDuration[sizeof("-Xprofile-duration:")-1 + PROPERTY_VALUE_MAX]; + char profileInterval[sizeof("-Xprofile-interval:")-1 + PROPERTY_VALUE_MAX]; + char profileBackoff[sizeof("-Xprofile-backoff:")-1 + PROPERTY_VALUE_MAX]; + char profileTopKThreshold[sizeof("-Xprofile-top-k-threshold:")-1 + PROPERTY_VALUE_MAX]; + char profileTopKChangeThreshold[sizeof("-Xprofile-top-k-change-threshold:")-1 + + PROPERTY_VALUE_MAX]; + char profileType[sizeof("-Xprofile-type:")-1 + PROPERTY_VALUE_MAX]; + char profileMaxStackDepth[sizeof("-Xprofile-max-stack-depth:")-1 + PROPERTY_VALUE_MAX]; char langOption[sizeof("-Duser.language=") + 3]; char regionOption[sizeof("-Duser.region=") + 3]; - char lockProfThresholdBuf[sizeof("-Xlockprofthreshold:") + sizeof(propBuf)]; - char jitOpBuf[sizeof("-Xjitop:") + PROPERTY_VALUE_MAX]; - char jitMethodBuf[sizeof("-Xjitmethod:") + PROPERTY_VALUE_MAX]; + char lockProfThresholdBuf[sizeof("-Xlockprofthreshold:")-1 + PROPERTY_VALUE_MAX]; + char jitOpBuf[sizeof("-Xjitop:")-1 + PROPERTY_VALUE_MAX]; + char jitMethodBuf[sizeof("-Xjitmethod:")-1 + PROPERTY_VALUE_MAX]; + bool checkJni = false; property_get("dalvik.vm.checkjni", propBuf, ""); if (strcmp(propBuf, "true") == 0) { checkJni = true; @@ -506,6 +585,20 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) checkJni = true; } } + ALOGD("CheckJNI is %s\n", checkJni ? "ON" : "OFF"); + if (checkJni) { + /* extended JNI checking */ + opt.optionString = "-Xcheck:jni"; + mOptions.add(opt); + + /* set a cap on JNI global references */ + opt.optionString = "-Xjnigreflimit:2000"; + mOptions.add(opt); + + /* with -Xcheck:jni, this provides a JNI function call trace */ + //opt.optionString = "-verbose:jni"; + //mOptions.add(opt); + } property_get("dalvik.vm.execution-mode", propBuf, ""); if (strcmp(propBuf, "int:portable") == 0) { @@ -516,23 +609,39 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) executionMode = kEMJitCompiler; } - property_get("dalvik.vm.stack-trace-file", stackTraceFileBuf, ""); + parseRuntimeOption("dalvik.vm.stack-trace-file", stackTraceFileBuf, "-Xstacktracefile:"); property_get("dalvik.vm.check-dex-sum", propBuf, ""); if (strcmp(propBuf, "true") == 0) { - checkDexSum = true; + /* perform additional DEX checksum tests */ + opt.optionString = "-Xcheckdexsum"; + mOptions.add(opt); } property_get("log.redirect-stdio", propBuf, ""); if (strcmp(propBuf, "true") == 0) { - logStdio = true; + /* convert stdout/stderr to log messages */ + opt.optionString = "-Xlog-stdio"; + mOptions.add(opt); } strcpy(enableAssertBuf, "-ea:"); - property_get("dalvik.vm.enableassertions", enableAssertBuf+4, ""); + property_get("dalvik.vm.enableassertions", enableAssertBuf+sizeof("-ea:")-1, ""); + if (enableAssertBuf[sizeof("-ea:")-1] != '\0') { + /* accept "all" to mean "all classes and packages" */ + if (strcmp(enableAssertBuf+sizeof("-ea:")-1, "all") == 0) + enableAssertBuf[3] = '\0'; // truncate to "-ea" + ALOGI("Assertions enabled: '%s'\n", enableAssertBuf); + opt.optionString = enableAssertBuf; + mOptions.add(opt); + } else { + ALOGV("Assertions disabled\n"); + } strcpy(jniOptsBuf, "-Xjniopts:"); - property_get("dalvik.vm.jniopts", jniOptsBuf+10, ""); + if (parseRuntimeOption("dalvik.vm.jniopts", jniOptsBuf, "-Xjniopts:")) { + ALOGI("JNI options: '%s'\n", jniOptsBuf); + } /* route exit() to our handler */ opt.extraInfo = (void*) runtime_exit; @@ -561,54 +670,24 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) * The default starting and maximum size of the heap. Larger * values should be specified in a product property override. */ - strcpy(heapstartsizeOptsBuf, "-Xms"); - property_get("dalvik.vm.heapstartsize", heapstartsizeOptsBuf+4, "4m"); - opt.optionString = heapstartsizeOptsBuf; - mOptions.add(opt); - strcpy(heapsizeOptsBuf, "-Xmx"); - property_get("dalvik.vm.heapsize", heapsizeOptsBuf+4, "16m"); - opt.optionString = heapsizeOptsBuf; - mOptions.add(opt); + parseRuntimeOption("dalvik.vm.heapstartsize", heapstartsizeOptsBuf, "-Xms", "4m"); + parseRuntimeOption("dalvik.vm.heapsize", heapsizeOptsBuf, "-Xmx", "16m"); // Increase the main thread's interpreter stack size for bug 6315322. opt.optionString = "-XX:mainThreadStackSize=24K"; mOptions.add(opt); // Set the max jit code cache size. Note: size of 0 will disable the JIT. - strcpy(jitcodecachesizeOptsBuf, "-Xjitcodecachesize:"); - property_get("dalvik.vm.jit.codecachesize", jitcodecachesizeOptsBuf+19, NULL); - if (jitcodecachesizeOptsBuf[19] != '\0') { - opt.optionString = jitcodecachesizeOptsBuf; - mOptions.add(opt); - } + parseRuntimeOption("dalvik.vm.jit.codecachesize", + jitcodecachesizeOptsBuf, + "-Xjitcodecachesize:"); - strcpy(heapgrowthlimitOptsBuf, "-XX:HeapGrowthLimit="); - property_get("dalvik.vm.heapgrowthlimit", heapgrowthlimitOptsBuf+20, ""); - if (heapgrowthlimitOptsBuf[20] != '\0') { - opt.optionString = heapgrowthlimitOptsBuf; - mOptions.add(opt); - } - - strcpy(heapminfreeOptsBuf, "-XX:HeapMinFree="); - property_get("dalvik.vm.heapminfree", heapminfreeOptsBuf+16, ""); - if (heapminfreeOptsBuf[16] != '\0') { - opt.optionString = heapminfreeOptsBuf; - mOptions.add(opt); - } - - strcpy(heapmaxfreeOptsBuf, "-XX:HeapMaxFree="); - property_get("dalvik.vm.heapmaxfree", heapmaxfreeOptsBuf+16, ""); - if (heapmaxfreeOptsBuf[16] != '\0') { - opt.optionString = heapmaxfreeOptsBuf; - mOptions.add(opt); - } - - strcpy(heaptargetutilizationOptsBuf, "-XX:HeapTargetUtilization="); - property_get("dalvik.vm.heaptargetutilization", heaptargetutilizationOptsBuf+26, ""); - if (heaptargetutilizationOptsBuf[26] != '\0') { - opt.optionString = heaptargetutilizationOptsBuf; - mOptions.add(opt); - } + parseRuntimeOption("dalvik.vm.heapgrowthlimit", heapgrowthlimitOptsBuf, "-XX:HeapGrowthLimit="); + parseRuntimeOption("dalvik.vm.heapminfree", heapminfreeOptsBuf, "-XX:HeapMinFree="); + parseRuntimeOption("dalvik.vm.heapmaxfree", heapmaxfreeOptsBuf, "-XX:HeapMaxFree="); + parseRuntimeOption("dalvik.vm.heaptargetutilization", + heaptargetutilizationOptsBuf, + "-XX:HeapTargetUtilization="); property_get("ro.config.low_ram", propBuf, ""); if (strcmp(propBuf, "true") == 0) { @@ -616,19 +695,8 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) mOptions.add(opt); } - strcpy(gctypeOptsBuf, "-Xgc:"); - property_get("dalvik.vm.gctype", gctypeOptsBuf+5, ""); - if (gctypeOptsBuf[5] != '\0') { - opt.optionString = gctypeOptsBuf; - mOptions.add(opt); - } - - strcpy(backgroundgcOptsBuf, "-XX:BackgroundGC="); - property_get("dalvik.vm.backgroundgctype", backgroundgcOptsBuf+sizeof("-XX:BackgroundGC=")-1, ""); - if (backgroundgcOptsBuf[sizeof("-XX:BackgroundGC=")-1] != '\0') { - opt.optionString = backgroundgcOptsBuf; - mOptions.add(opt); - } + parseRuntimeOption("dalvik.vm.gctype", gctypeOptsBuf, "-Xgc:"); + parseRuntimeOption("dalvik.vm.backgroundgctype", backgroundgcOptsBuf, "-XX:BackgroundGC="); /* * Enable or disable dexopt features, such as bytecode verification and @@ -687,46 +755,15 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) "-agentlib:jdwp=transport=dt_android_adb,suspend=n,server=y"; mOptions.add(opt); - ALOGD("CheckJNI is %s\n", checkJni ? "ON" : "OFF"); - if (checkJni) { - /* extended JNI checking */ - opt.optionString = "-Xcheck:jni"; - mOptions.add(opt); - - /* set a cap on JNI global references */ - opt.optionString = "-Xjnigreflimit:2000"; - mOptions.add(opt); - - /* with -Xcheck:jni, this provides a JNI function call trace */ - //opt.optionString = "-verbose:jni"; - //mOptions.add(opt); - } - - property_get("dalvik.vm.lockprof.threshold", propBuf, ""); - if (strlen(propBuf) > 0) { - strcpy(lockProfThresholdBuf, "-Xlockprofthreshold:"); - strcat(lockProfThresholdBuf, propBuf); - opt.optionString = lockProfThresholdBuf; - mOptions.add(opt); - } + parseRuntimeOption("dalvik.vm.lockprof.threshold", + lockProfThresholdBuf, + "-Xlockprofthreshold:"); /* Force interpreter-only mode for selected opcodes. Eg "1-0a,3c,f1-ff" */ - property_get("dalvik.vm.jit.op", propBuf, ""); - if (strlen(propBuf) > 0) { - strcpy(jitOpBuf, "-Xjitop:"); - strcat(jitOpBuf, propBuf); - opt.optionString = jitOpBuf; - mOptions.add(opt); - } + parseRuntimeOption("dalvik.vm.jit.op", jitOpBuf, "-Xjitop:"); /* Force interpreter-only mode for selected methods */ - property_get("dalvik.vm.jit.method", propBuf, ""); - if (strlen(propBuf) > 0) { - strcpy(jitMethodBuf, "-Xjitmethod:"); - strcat(jitMethodBuf, propBuf); - opt.optionString = jitMethodBuf; - mOptions.add(opt); - } + parseRuntimeOption("dalvik.vm.jit.method", jitMethodBuf, "-Xjitmethod:"); if (executionMode == kEMIntPortable) { opt.optionString = "-Xint:portable"; @@ -739,58 +776,26 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) mOptions.add(opt); } - if (checkDexSum) { - /* perform additional DEX checksum tests */ - opt.optionString = "-Xcheckdexsum"; - mOptions.add(opt); - } - - if (logStdio) { - /* convert stdout/stderr to log messages */ - opt.optionString = "-Xlog-stdio"; - mOptions.add(opt); - } - - if (enableAssertBuf[4] != '\0') { - /* accept "all" to mean "all classes and packages" */ - if (strcmp(enableAssertBuf+4, "all") == 0) - enableAssertBuf[3] = '\0'; - ALOGI("Assertions enabled: '%s'\n", enableAssertBuf); - opt.optionString = enableAssertBuf; - mOptions.add(opt); - } else { - ALOGV("Assertions disabled\n"); - } - - if (jniOptsBuf[10] != '\0') { - ALOGI("JNI options: '%s'\n", jniOptsBuf); - opt.optionString = jniOptsBuf; - mOptions.add(opt); - } - - if (stackTraceFileBuf[0] != '\0') { - static const char* stfOptName = "-Xstacktracefile:"; - - stackTraceFile = (char*) malloc(strlen(stfOptName) + - strlen(stackTraceFileBuf) +1); - strcpy(stackTraceFile, stfOptName); - strcat(stackTraceFile, stackTraceFileBuf); - opt.optionString = stackTraceFile; - mOptions.add(opt); - } - // libart tolerates libdvm flags, but not vice versa, so only pass some options if libart. property_get("persist.sys.dalvik.vm.lib.2", dalvikVmLibBuf, "libart.so"); bool libart = (strncmp(dalvikVmLibBuf, "libart", 6) == 0); if (libart) { - // Extra options for DexClassLoader. - property_get("dalvik.vm.dex2oat-flags", dex2oatFlagsBuf, ""); - parseExtraOpts(dex2oatFlagsBuf, "-Xcompiler-option"); - // Extra options for boot.art/boot.oat image generation. + parseCompilerRuntimeOption("dalvik.vm.image-dex2oat-Xms", dex2oatXmsImageFlagsBuf, + "-Xms", "-Ximage-compiler-option"); + parseCompilerRuntimeOption("dalvik.vm.image-dex2oat-Xmx", dex2oatXmxImageFlagsBuf, + "-Xmx", "-Ximage-compiler-option"); property_get("dalvik.vm.image-dex2oat-flags", dex2oatImageFlagsBuf, ""); parseExtraOpts(dex2oatImageFlagsBuf, "-Ximage-compiler-option"); + + // Extra options for DexClassLoader. + parseCompilerRuntimeOption("dalvik.vm.dex2oat-Xms", dex2oatXmsFlagsBuf, + "-Xms", "-Xcompiler-option"); + parseCompilerRuntimeOption("dalvik.vm.dex2oat-Xmx", dex2oatXmxFlagsBuf, + "-Xmx", "-Xcompiler-option"); + property_get("dalvik.vm.dex2oat-flags", dex2oatFlagsBuf, ""); + parseExtraOpts(dex2oatFlagsBuf, "-Xcompiler-option"); } /* extra options; parse this late so it overrides others */ @@ -829,49 +834,42 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) } // Number of seconds during profile runs. - strcpy(profile_period, "-Xprofile-period:"); - if (property_get("dalvik.vm.profile.period-secs", profile_period+17, NULL) > 0) { - opt.optionString = profile_period; - mOptions.add(opt); - } + parseRuntimeOption("dalvik.vm.profile.period-secs", profilePeriod, "-Xprofile-period:"); // Length of each profile run (seconds). - strcpy(profile_duration, "-Xprofile-duration:"); - if (property_get("dalvik.vm.profile.duration-secs", profile_duration+19, NULL) > 0) { - opt.optionString = profile_duration; - mOptions.add(opt); - } + parseRuntimeOption("dalvik.vm.profile.duration-secs", + profileDuration, + "-Xprofile-duration:"); // Polling interval during profile run (microseconds). - strcpy(profile_interval, "-Xprofile-interval:"); - if (property_get("dalvik.vm.profile.interval-us", profile_interval+19, NULL) > 0) { - opt.optionString = profile_interval; - mOptions.add(opt); - } + parseRuntimeOption("dalvik.vm.profile.interval-us", profileInterval, "-Xprofile-interval:"); // Coefficient for period backoff. The the period is multiplied // by this value after each profile run. - strcpy(profile_backoff, "-Xprofile-backoff:"); - if (property_get("dalvik.vm.profile.backoff-coeff", profile_backoff+18, NULL) > 0) { - opt.optionString = profile_backoff; - mOptions.add(opt); - } - - // Top K% of samples that are considered relevant when deciding if the app should be recompiled. - strcpy(profile_top_k_threshold, "-Xprofile-top-k-threshold:"); - if (property_get("dalvik.vm.profile.top-k-thr", profile_top_k_threshold+26, NULL) > 0) { - opt.optionString = profile_top_k_threshold; - mOptions.add(opt); - } - - // The threshold after which a change in the structure of the top K% profiled samples becomes significant - // and triggers recompilation. A change in profile is considered significant if X% (top-k-change-threshold) - // of the top K% (top-k-threshold property) samples has changed. - strcpy(profile_top_k_change_threshold, "-Xprofile-top-k-change-threshold:"); - if (property_get("dalvik.vm.profile.top-k-ch-thr", profile_top_k_change_threshold+33, NULL) > 0) { - opt.optionString = profile_top_k_change_threshold; - mOptions.add(opt); - } + parseRuntimeOption("dalvik.vm.profile.backoff-coeff", profileBackoff, "-Xprofile-backoff:"); + + // Top K% of samples that are considered relevant when + // deciding if the app should be recompiled. + parseRuntimeOption("dalvik.vm.profile.top-k-thr", + profileTopKThreshold, + "-Xprofile-top-k-threshold:"); + + // The threshold after which a change in the structure of the + // top K% profiled samples becomes significant and triggers + // recompilation. A change in profile is considered + // significant if X% (top-k-change-threshold) of the top K% + // (top-k-threshold property) samples has changed. + parseRuntimeOption("dalvik.vm.profile.top-k-ch-thr", + profileTopKChangeThreshold, + "-Xprofile-top-k-change-threshold:"); + + // Type of profile data. + parseRuntimeOption("dalvik.vm.profiler.type", profileType, "-Xprofile-type:"); + + // Depth of bounded stack data + parseRuntimeOption("dalvik.vm.profile.max-stack-depth", + profileMaxStackDepth, + "-Xprofile-max-stack-depth:"); } initArgs.version = JNI_VERSION_1_4; @@ -894,7 +892,6 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) result = 0; bail: - free(stackTraceFile); return result; } @@ -1218,7 +1215,6 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_util_EventLog), REG_JNI(register_android_util_Log), REG_JNI(register_android_util_FloatMath), - REG_JNI(register_android_text_format_Time), REG_JNI(register_android_content_AssetManager), REG_JNI(register_android_content_StringBlock), REG_JNI(register_android_content_XmlBlock), diff --git a/core/jni/Time.cpp b/core/jni/Time.cpp deleted file mode 100644 index f3037f3..0000000 --- a/core/jni/Time.cpp +++ /dev/null @@ -1,199 +0,0 @@ -#include "TimeUtils.h" -#include <stdio.h> -#include <cutils/tztime.h> - -namespace android { - -static void -dump(const Time& t) -{ - #ifdef HAVE_TM_GMTOFF - long tm_gmtoff = t.t.tm_gmtoff; - #else - long tm_gmtoff = 0; - #endif - printf("%04d-%02d-%02d %02d:%02d:%02d (%d,%ld,%d,%d)\n", - t.t.tm_year+1900, t.t.tm_mon+1, t.t.tm_mday, - t.t.tm_hour, t.t.tm_min, t.t.tm_sec, - t.t.tm_isdst, tm_gmtoff, t.t.tm_wday, t.t.tm_yday); -} - -Time::Time() -{ - t.tm_sec = 0; - t.tm_min = 0; - t.tm_hour = 0; - t.tm_mday = 0; - t.tm_mon = 0; - t.tm_year = 0; - t.tm_wday = 0; - t.tm_yday = 0; - t.tm_isdst = -1; // we don't know, so let the C library determine - #ifdef HAVE_TM_GMTOFF - t.tm_gmtoff = 0; - #endif -} - - -#define COMPARE_FIELD(field) do { \ - int diff = a.t.field - b.t.field; \ - if (diff != 0) return diff; \ - } while(0) - -int -Time::compare(Time& a, Time& b) -{ - if (0 == strcmp(a.timezone, b.timezone)) { - // if the timezones are the same, we can easily compare the two - // times. Otherwise, convert to milliseconds and compare that. - // This requires that object be normalized. - COMPARE_FIELD(tm_year); - COMPARE_FIELD(tm_mon); - COMPARE_FIELD(tm_mday); - COMPARE_FIELD(tm_hour); - COMPARE_FIELD(tm_min); - COMPARE_FIELD(tm_sec); - return 0; - } else { - int64_t am = a.toMillis(false /* use isDst */); - int64_t bm = b.toMillis(false /* use isDst */); - int64_t diff = am-bm; - return (diff < 0) ? -1 : ((diff > 0) ? 1 : 0); - } -} - -static const int DAYS_PER_MONTH[] = { - 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 - }; - -static inline int days_this_month(int year, int month) -{ - int n = DAYS_PER_MONTH[month]; - if (n != 28) { - return n; - } else { - int y = year; - return ((y%4)==0&&((y%100)!=0||(y%400)==0)) ? 29 : 28; - } -} - -void -Time::switchTimezone(const char* timezone) -{ - time_t seconds = mktime_tz(&(this->t), this->timezone); - localtime_tz(&seconds, &(this->t), timezone); -} - -String8 -Time::format(const char *format, const struct strftime_locale *locale) const -{ - char buf[257]; - int n = strftime_tz(buf, 257, format, &(this->t), locale); - if (n > 0) { - return String8(buf); - } else { - return String8(); - } -} - -static inline short -tochar(int n) -{ - return (n >= 0 && n <= 9) ? ('0'+n) : ' '; -} - -static inline short -next_char(int *m, int k) -{ - int n = *m / k; - *m = *m % k; - return tochar(n); -} - -void -Time::format2445(short* buf, bool hasTime) const -{ - int n; - - n = t.tm_year+1900; - buf[0] = next_char(&n, 1000); - buf[1] = next_char(&n, 100); - buf[2] = next_char(&n, 10); - buf[3] = tochar(n); - - n = t.tm_mon+1; - buf[4] = next_char(&n, 10); - buf[5] = tochar(n); - - n = t.tm_mday; - buf[6] = next_char(&n, 10); - buf[7] = tochar(n); - - if (hasTime) { - buf[8] = 'T'; - - n = t.tm_hour; - buf[9] = next_char(&n, 10); - buf[10] = tochar(n); - - n = t.tm_min; - buf[11] = next_char(&n, 10); - buf[12] = tochar(n); - - n = t.tm_sec; - buf[13] = next_char(&n, 10); - buf[14] = tochar(n); - bool inUtc = strcmp("UTC", timezone) == 0; - if (inUtc) { - buf[15] = 'Z'; - } - } -} - -String8 -Time::toString() const -{ - String8 str; - char* s = str.lockBuffer(150); - #ifdef HAVE_TM_GMTOFF - long tm_gmtoff = t.tm_gmtoff; - #else - long tm_gmtoff = 0; - #endif - sprintf(s, "%04d%02d%02dT%02d%02d%02d%s(%d,%d,%ld,%d,%d)", - t.tm_year+1900, t.tm_mon+1, t.tm_mday, t.tm_hour, t.tm_min, - t.tm_sec, timezone, t.tm_wday, t.tm_yday, tm_gmtoff, t.tm_isdst, - (int)(((Time*)this)->toMillis(false /* use isDst */)/1000)); - str.unlockBuffer(); - return str; -} - -void -Time::setToNow() -{ - time_t seconds; - time(&seconds); - localtime_tz(&seconds, &(this->t), this->timezone); -} - -int64_t -Time::toMillis(bool ignoreDst) -{ - if (ignoreDst) { - this->t.tm_isdst = -1; - } - int64_t r = mktime_tz(&(this->t), this->timezone); - if (r == -1) - return -1; - return r * 1000; -} - -void -Time::set(int64_t millis) -{ - time_t seconds = millis / 1000; - localtime_tz(&seconds, &(this->t), this->timezone); -} - -}; // namespace android - diff --git a/core/jni/TimeUtils.h b/core/jni/TimeUtils.h deleted file mode 100644 index b19e021..0000000 --- a/core/jni/TimeUtils.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2005 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_TIME_H -#define ANDROID_TIME_H - -#include <time.h> -#include <cutils/tztime.h> -#include <stdint.h> -#include <sys/types.h> -#include <sys/time.h> -#include <utils/String8.h> -#include <utils/String16.h> - -namespace android { - -/* - * This class is the core implementation of the android.util.Time java - * class. It doesn't implement some of the methods that are implemented - * in Java. They could be done here, but it's not expected that this class - * will be used. If that assumption is incorrect, feel free to update this - * file. The reason to do it here is to not mix the implementation of this - * class and the jni glue code. - */ -class Time -{ -public: - struct tm t; - - // this object doesn't own this string - const char *timezone; - - enum { - SEC = 1, - MIN = 2, - HOUR = 3, - MDAY = 4, - MON = 5, - YEAR = 6, - WDAY = 7, - YDAY = 8 - }; - - static int compare(Time& a, Time& b); - - Time(); - - void switchTimezone(const char *timezone); - String8 format(const char *format, const struct strftime_locale *locale) const; - void format2445(short* buf, bool hasTime) const; - String8 toString() const; - void setToNow(); - int64_t toMillis(bool ignoreDst); - void set(int64_t millis); - - inline void set(int sec, int min, int hour, int mday, int mon, int year, - int isdst) - { - this->t.tm_sec = sec; - this->t.tm_min = min; - this->t.tm_hour = hour; - this->t.tm_mday = mday; - this->t.tm_mon = mon; - this->t.tm_year = year; - this->t.tm_isdst = isdst; -#ifdef HAVE_TM_GMTOFF - this->t.tm_gmtoff = 0; -#endif - this->t.tm_wday = 0; - this->t.tm_yday = 0; - } -}; - -}; // namespace android - -#endif // ANDROID_TIME_H diff --git a/core/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp index ab70f25..3f7df50 100644 --- a/core/jni/android_media_AudioRecord.cpp +++ b/core/jni/android_media_AudioRecord.cpp @@ -399,13 +399,46 @@ static jint android_media_AudioRecord_readInShortArray(JNIEnv *env, jobject thi jshortArray javaAudioData, jint offsetInShorts, jint sizeInShorts) { - jint read = android_media_AudioRecord_readInByteArray(env, thiz, - (jbyteArray) javaAudioData, - offsetInShorts*2, sizeInShorts*2); - if (read > 0) { - read /= 2; + jshort* recordBuff = NULL; + // get the audio recorder from which we'll read new audio samples + sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz); + if (lpRecorder == NULL) { + ALOGE("Unable to retrieve AudioRecord object, can't record"); + return 0; } - return read; + + if (!javaAudioData) { + ALOGE("Invalid Java array to store recorded audio, can't record"); + return 0; + } + + // get the pointer to where we'll record the audio + // NOTE: We may use GetPrimitiveArrayCritical() when the JNI implementation changes in such + // a way that it becomes much more efficient. When doing so, we will have to prevent the + // AudioSystem callback to be called while in critical section (in case of media server + // process crash for instance) + recordBuff = (jshort *)env->GetShortArrayElements(javaAudioData, NULL); + + if (recordBuff == NULL) { + ALOGE("Error retrieving destination for recorded audio data, can't record"); + return 0; + } + + // read the new audio data from the native AudioRecord object + const size_t recorderBuffSize = lpRecorder->frameCount()*lpRecorder->frameSize(); + const size_t sizeInBytes = sizeInShorts * sizeof(short); + ssize_t readSize = lpRecorder->read(recordBuff + offsetInShorts * sizeof(short), + sizeInBytes > recorderBuffSize ? + recorderBuffSize : sizeInBytes); + + env->ReleaseShortArrayElements(javaAudioData, recordBuff, 0); + + if (readSize < 0) { + readSize = AUDIORECORD_ERROR_INVALID_OPERATION; + } else { + readSize /= sizeof(short); + } + return (jint) readSize; } // ---------------------------------------------------------------------------- diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp index dc8d9d8..1bf42e9 100644 --- a/core/jni/android_media_AudioTrack.cpp +++ b/core/jni/android_media_AudioTrack.cpp @@ -595,13 +595,39 @@ static jint android_media_AudioTrack_native_write_short(JNIEnv *env, jobject th jshortArray javaAudioData, jint offsetInShorts, jint sizeInShorts, jint javaAudioFormat) { - jint written = android_media_AudioTrack_native_write_byte(env, thiz, - (jbyteArray) javaAudioData, - offsetInShorts*2, sizeInShorts*2, - javaAudioFormat); + sp<AudioTrack> lpTrack = getAudioTrack(env, thiz); + if (lpTrack == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", + "Unable to retrieve AudioTrack pointer for write()"); + return 0; + } + + // get the pointer for the audio data from the java array + // NOTE: We may use GetPrimitiveArrayCritical() when the JNI implementation changes in such + // a way that it becomes much more efficient. When doing so, we will have to prevent the + // AudioSystem callback to be called while in critical section (in case of media server + // process crash for instance) + jshort* cAudioData = NULL; + if (javaAudioData) { + cAudioData = (jshort *)env->GetShortArrayElements(javaAudioData, NULL); + if (cAudioData == NULL) { + ALOGE("Error retrieving source of audio data to play, can't play"); + return 0; // out of memory or no data to load + } + } else { + ALOGE("NULL java array of audio data to play, can't play"); + return 0; + } + jint written = writeToTrack(lpTrack, javaAudioFormat, (jbyte *)cAudioData, + offsetInShorts * sizeof(short), sizeInShorts * sizeof(short)); + env->ReleaseShortArrayElements(javaAudioData, cAudioData, 0); + if (written > 0) { - written /= 2; + written /= sizeof(short); } + //ALOGV("write wrote %d (tried %d) shorts in the native AudioTrack with offset %d", + // (int)written, (int)(sizeInShorts), (int)offsetInShorts); + return written; } diff --git a/core/jni/android_text_format_Time.cpp b/core/jni/android_text_format_Time.cpp deleted file mode 100644 index 28a8a5d..0000000 --- a/core/jni/android_text_format_Time.cpp +++ /dev/null @@ -1,689 +0,0 @@ -/* -** Copyright 2006, 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_TAG "Log_println" - -#include <utils/Log.h> -#include <utils/String8.h> -#include <assert.h> - -#include "jni.h" -#include "utils/misc.h" -#include "android_runtime/AndroidRuntime.h" -#include "ScopedStringChars.h" -#include "TimeUtils.h" -#include <nativehelper/JNIHelp.h> -#include <cutils/tztime.h> - -namespace android { - -static jfieldID g_allDayField = 0; -static jfieldID g_secField = 0; -static jfieldID g_minField = 0; -static jfieldID g_hourField = 0; -static jfieldID g_mdayField = 0; -static jfieldID g_monField = 0; -static jfieldID g_yearField = 0; -static jfieldID g_wdayField = 0; -static jfieldID g_ydayField = 0; -static jfieldID g_isdstField = 0; -static jfieldID g_gmtoffField = 0; -static jfieldID g_timezoneField = 0; - -static jfieldID g_shortMonthsField = 0; -static jfieldID g_longMonthsField = 0; -static jfieldID g_longStandaloneMonthsField = 0; -static jfieldID g_shortWeekdaysField = 0; -static jfieldID g_longWeekdaysField = 0; -static jfieldID g_timeOnlyFormatField = 0; -static jfieldID g_dateOnlyFormatField = 0; -static jfieldID g_dateTimeFormatField = 0; -static jfieldID g_amField = 0; -static jfieldID g_pmField = 0; -static jfieldID g_dateCommandField = 0; -static jfieldID g_localeField = 0; - -static jclass g_timeClass = NULL; - -static inline bool java2time(JNIEnv* env, Time* t, jobject o) -{ - t->t.tm_sec = env->GetIntField(o, g_secField); - t->t.tm_min = env->GetIntField(o, g_minField); - t->t.tm_hour = env->GetIntField(o, g_hourField); - t->t.tm_mday = env->GetIntField(o, g_mdayField); - t->t.tm_mon = env->GetIntField(o, g_monField); - t->t.tm_year = (env->GetIntField(o, g_yearField))-1900; - t->t.tm_wday = env->GetIntField(o, g_wdayField); - t->t.tm_yday = env->GetIntField(o, g_ydayField); - t->t.tm_isdst = env->GetIntField(o, g_isdstField); - t->t.tm_gmtoff = env->GetLongField(o, g_gmtoffField); - bool allDay = env->GetBooleanField(o, g_allDayField); - if (allDay && - ((t->t.tm_sec !=0) || (t->t.tm_min != 0) || (t->t.tm_hour != 0))) { - jniThrowException(env, "java/lang/IllegalArgumentException", - "allDay is true but sec, min, hour are not 0."); - return false; - } - return true; -} - -static inline void time2java(JNIEnv* env, jobject o, const Time &t) -{ - env->SetIntField(o, g_secField, t.t.tm_sec); - env->SetIntField(o, g_minField, t.t.tm_min); - env->SetIntField(o, g_hourField, t.t.tm_hour); - env->SetIntField(o, g_mdayField, t.t.tm_mday); - env->SetIntField(o, g_monField, t.t.tm_mon); - env->SetIntField(o, g_yearField, t.t.tm_year+1900); - env->SetIntField(o, g_wdayField, t.t.tm_wday); - env->SetIntField(o, g_ydayField, t.t.tm_yday); - env->SetIntField(o, g_isdstField, t.t.tm_isdst); - env->SetLongField(o, g_gmtoffField, t.t.tm_gmtoff); -} - -#define ACQUIRE_TIMEZONE(This, t) \ - jstring timezoneString_##This \ - = (jstring) env->GetObjectField(This, g_timezoneField); \ - t.timezone = env->GetStringUTFChars(timezoneString_##This, NULL); - -#define RELEASE_TIMEZONE(This, t) \ - env->ReleaseStringUTFChars(timezoneString_##This, t.timezone); - - -// ============================================================================ - -static jlong android_text_format_Time_normalize(JNIEnv* env, jobject This, - jboolean ignoreDst) -{ - Time t; - if (!java2time(env, &t, This)) return 0L; - ACQUIRE_TIMEZONE(This, t) - - int64_t result = t.toMillis(ignoreDst != 0); - - time2java(env, This, t); - RELEASE_TIMEZONE(This, t) - - return static_cast<jlong>(result); -} - -static void android_text_format_Time_switchTimezone(JNIEnv* env, jobject This, - jstring timezoneObject) -{ - Time t; - if (!java2time(env, &t, This)) return; - ACQUIRE_TIMEZONE(This, t) - - const char* timezone = env->GetStringUTFChars(timezoneObject, NULL); - - t.switchTimezone(timezone); - - time2java(env, This, t); - env->ReleaseStringUTFChars(timezoneObject, timezone); - RELEASE_TIMEZONE(This, t) - - // we do this here because there's no point in reallocating the string - env->SetObjectField(This, g_timezoneField, timezoneObject); -} - -static jint android_text_format_Time_compare(JNIEnv* env, jobject clazz, - jobject aObject, jobject bObject) -{ - Time a, b; - - if (!java2time(env, &a, aObject)) return 0; - ACQUIRE_TIMEZONE(aObject, a) - - if (!java2time(env, &b, bObject)) return 0; - ACQUIRE_TIMEZONE(bObject, b) - - int result = Time::compare(a, b); - - RELEASE_TIMEZONE(aObject, a) - RELEASE_TIMEZONE(bObject, b) - - return static_cast<jint>(result); -} - -static jstring android_text_format_Time_format2445(JNIEnv* env, jobject This) -{ - Time t; - if (!java2time(env, &t, This)) return env->NewStringUTF(""); - bool allDay = env->GetBooleanField(This, g_allDayField); - - if (!allDay) { - ACQUIRE_TIMEZONE(This, t) - bool inUtc = strcmp("UTC", t.timezone) == 0; - short buf[16]; - t.format2445(buf, true); - RELEASE_TIMEZONE(This, t) - if (inUtc) { - // The letter 'Z' is appended to the end so allow for one - // more character in the buffer. - return env->NewString((jchar*)buf, 16); - } else { - return env->NewString((jchar*)buf, 15); - } - } else { - short buf[8]; - t.format2445(buf, false); - return env->NewString((jchar*)buf, 8); - } -} - -static jstring android_text_format_Time_format(JNIEnv* env, jobject This, - jstring formatObject) -{ - // We only teardown and setup our 'locale' struct and other state - // when the Java-side locale changed. This is safe to do here - // without locking because we're always called from Java code - // synchronized on the class instance. - static jobject js_locale_previous = NULL; - static struct strftime_locale locale; - static jstring js_mon[12], js_month[12], js_wday[7], js_weekday[7]; - static jstring js_standalone_month[12]; - static jstring js_X_fmt, js_x_fmt, js_c_fmt, js_am, js_pm, js_date_fmt; - - Time t; - if (!java2time(env, &t, This)) return env->NewStringUTF(""); - - jclass timeClass = g_timeClass; - jobject js_locale = (jobject) env->GetStaticObjectField(timeClass, g_localeField); - if (js_locale_previous != js_locale) { - if (js_locale_previous != NULL) { - // Free the old one. - for (int i = 0; i < 12; i++) { - env->ReleaseStringUTFChars(js_mon[i], locale.mon[i]); - env->ReleaseStringUTFChars(js_month[i], locale.month[i]); - env->ReleaseStringUTFChars(js_standalone_month[i], locale.standalone_month[i]); - env->DeleteGlobalRef(js_mon[i]); - env->DeleteGlobalRef(js_month[i]); - env->DeleteGlobalRef(js_standalone_month[i]); - } - - for (int i = 0; i < 7; i++) { - env->ReleaseStringUTFChars(js_wday[i], locale.wday[i]); - env->ReleaseStringUTFChars(js_weekday[i], locale.weekday[i]); - env->DeleteGlobalRef(js_wday[i]); - env->DeleteGlobalRef(js_weekday[i]); - } - - env->ReleaseStringUTFChars(js_X_fmt, locale.X_fmt); - env->ReleaseStringUTFChars(js_x_fmt, locale.x_fmt); - env->ReleaseStringUTFChars(js_c_fmt, locale.c_fmt); - env->ReleaseStringUTFChars(js_am, locale.am); - env->ReleaseStringUTFChars(js_pm, locale.pm); - env->ReleaseStringUTFChars(js_date_fmt, locale.date_fmt); - env->DeleteGlobalRef(js_X_fmt); - env->DeleteGlobalRef(js_x_fmt); - env->DeleteGlobalRef(js_c_fmt); - env->DeleteGlobalRef(js_am); - env->DeleteGlobalRef(js_pm); - env->DeleteGlobalRef(js_date_fmt); - } - js_locale_previous = js_locale; - - jobjectArray ja; - ja = (jobjectArray) env->GetStaticObjectField(timeClass, g_shortMonthsField); - for (int i = 0; i < 12; i++) { - // Calendar.JANUARY == 0. - js_mon[i] = (jstring) env->NewGlobalRef(env->GetObjectArrayElement(ja, i)); - locale.mon[i] = env->GetStringUTFChars(js_mon[i], NULL); - } - - ja = (jobjectArray) env->GetStaticObjectField(timeClass, g_longMonthsField); - for (int i = 0; i < 12; i++) { - // Calendar.JANUARY == 0. - js_month[i] = (jstring) env->NewGlobalRef(env->GetObjectArrayElement(ja, i)); - locale.month[i] = env->GetStringUTFChars(js_month[i], NULL); - } - - ja = (jobjectArray) env->GetStaticObjectField(timeClass, g_longStandaloneMonthsField); - for (int i = 0; i < 12; i++) { - // Calendar.JANUARY == 0. - js_standalone_month[i] = (jstring) env->NewGlobalRef(env->GetObjectArrayElement(ja, i)); - locale.standalone_month[i] = env->GetStringUTFChars(js_standalone_month[i], NULL); - } - - ja = (jobjectArray) env->GetStaticObjectField(timeClass, g_shortWeekdaysField); - for (int i = 0; i < 7; i++) { - // Calendar.SUNDAY == 1, and there's an empty string in element 0. - js_wday[i] = (jstring) env->NewGlobalRef(env->GetObjectArrayElement(ja, i + 1)); - locale.wday[i] = env->GetStringUTFChars(js_wday[i], NULL); - } - - ja = (jobjectArray) env->GetStaticObjectField(timeClass, g_longWeekdaysField); - for (int i = 0; i < 7; i++) { - // Calendar.SUNDAY == 1, and there's an empty string in element 0. - js_weekday[i] = (jstring) env->NewGlobalRef(env->GetObjectArrayElement(ja, i + 1)); - locale.weekday[i] = env->GetStringUTFChars(js_weekday[i], NULL); - } - - js_X_fmt = (jstring) env->NewGlobalRef(env->GetStaticObjectField( - timeClass, g_timeOnlyFormatField)); - locale.X_fmt = env->GetStringUTFChars(js_X_fmt, NULL); - - js_x_fmt = (jstring) env->NewGlobalRef(env->GetStaticObjectField( - timeClass, g_dateOnlyFormatField)); - locale.x_fmt = env->GetStringUTFChars(js_x_fmt, NULL); - - js_c_fmt = (jstring) env->NewGlobalRef(env->GetStaticObjectField( - timeClass, g_dateTimeFormatField)); - locale.c_fmt = env->GetStringUTFChars(js_c_fmt, NULL); - - js_am = (jstring) env->NewGlobalRef(env->GetStaticObjectField( - timeClass, g_amField)); - locale.am = env->GetStringUTFChars(js_am, NULL); - - js_pm = (jstring) env->NewGlobalRef(env->GetStaticObjectField( - timeClass, g_pmField)); - locale.pm = env->GetStringUTFChars(js_pm, NULL); - - js_date_fmt = (jstring) env->NewGlobalRef(env->GetStaticObjectField( - timeClass, g_dateCommandField)); - locale.date_fmt = env->GetStringUTFChars(js_date_fmt, NULL); - } - - ACQUIRE_TIMEZONE(This, t) - - const char* format = env->GetStringUTFChars(formatObject, NULL); - - String8 r = t.format(format, &locale); - - env->ReleaseStringUTFChars(formatObject, format); - RELEASE_TIMEZONE(This, t) - - return env->NewStringUTF(r.string()); -} - - -static jstring android_text_format_Time_toString(JNIEnv* env, jobject This) -{ - Time t; - if (!java2time(env, &t, This)) return env->NewStringUTF(""); - ACQUIRE_TIMEZONE(This, t) - - String8 r = t.toString(); - - RELEASE_TIMEZONE(This, t) - - return env->NewStringUTF(r.string()); -} - -static void android_text_format_Time_setToNow(JNIEnv* env, jobject This) -{ - env->SetBooleanField(This, g_allDayField, JNI_FALSE); - Time t; - ACQUIRE_TIMEZONE(This, t) - - t.setToNow(); - - time2java(env, This, t); - RELEASE_TIMEZONE(This, t) -} - -static jlong android_text_format_Time_toMillis(JNIEnv* env, jobject This, - jboolean ignoreDst) -{ - Time t; - if (!java2time(env, &t, This)) return 0L; - ACQUIRE_TIMEZONE(This, t) - - int64_t result = t.toMillis(ignoreDst != 0); - - RELEASE_TIMEZONE(This, t) - - return static_cast<jlong>(result); -} - -static void android_text_format_Time_set(JNIEnv* env, jobject This, jlong millis) -{ - env->SetBooleanField(This, g_allDayField, JNI_FALSE); - Time t; - ACQUIRE_TIMEZONE(This, t) - - t.set(millis); - - time2java(env, This, t); - RELEASE_TIMEZONE(This, t) -} - - -// ============================================================================ -// Just do this here because it's not worth recreating the strings - -static int get_char(JNIEnv* env, const ScopedStringChars& s, int spos, int mul, - bool* thrown) -{ - jchar c = s[spos]; - if (c >= '0' && c <= '9') { - return (c - '0') * mul; - } else { - if (!*thrown) { - jniThrowExceptionFmt(env, "android/util/TimeFormatException", - "Parse error at pos=%d", spos); - *thrown = true; - } - return 0; - } -} - -static bool check_char(JNIEnv* env, const ScopedStringChars& s, int spos, jchar expected) -{ - jchar c = s[spos]; - if (c != expected) { - jniThrowExceptionFmt(env, "android/util/TimeFormatException", - "Unexpected character 0x%02x at pos=%d. Expected %c.", - c, spos, expected); - return false; - } - return true; -} - - -static jboolean android_text_format_Time_parse(JNIEnv* env, jobject This, jstring strObj) -{ - jsize len = env->GetStringLength(strObj); - if (len < 8) { - jniThrowException(env, "android/util/TimeFormatException", - "String too short -- expected at least 8 characters."); - return JNI_FALSE; - } - - jboolean inUtc = JNI_FALSE; - - ScopedStringChars s(env, strObj); - - // year - int n; - bool thrown = false; - n = get_char(env, s, 0, 1000, &thrown); - n += get_char(env, s, 1, 100, &thrown); - n += get_char(env, s, 2, 10, &thrown); - n += get_char(env, s, 3, 1, &thrown); - if (thrown) return JNI_FALSE; - env->SetIntField(This, g_yearField, n); - - // month - n = get_char(env, s, 4, 10, &thrown); - n += get_char(env, s, 5, 1, &thrown); - n--; - if (thrown) return JNI_FALSE; - env->SetIntField(This, g_monField, n); - - // day of month - n = get_char(env, s, 6, 10, &thrown); - n += get_char(env, s, 7, 1, &thrown); - if (thrown) return JNI_FALSE; - env->SetIntField(This, g_mdayField, n); - - if (len > 8) { - // T - if (!check_char(env, s, 8, 'T')) return JNI_FALSE; - env->SetBooleanField(This, g_allDayField, JNI_FALSE); - - // hour - n = get_char(env, s, 9, 10, &thrown); - n += get_char(env, s, 10, 1, &thrown); - if (thrown) return JNI_FALSE; - env->SetIntField(This, g_hourField, n); - - // min - n = get_char(env, s, 11, 10, &thrown); - n += get_char(env, s, 12, 1, &thrown); - if (thrown) return JNI_FALSE; - env->SetIntField(This, g_minField, n); - - // sec - n = get_char(env, s, 13, 10, &thrown); - n += get_char(env, s, 14, 1, &thrown); - if (thrown) return JNI_FALSE; - env->SetIntField(This, g_secField, n); - - if (len > 15) { - // Z - if (!check_char(env, s, 15, 'Z')) return JNI_FALSE; - inUtc = JNI_TRUE; - } - } else { - env->SetBooleanField(This, g_allDayField, JNI_TRUE); - env->SetIntField(This, g_hourField, 0); - env->SetIntField(This, g_minField, 0); - env->SetIntField(This, g_secField, 0); - } - - env->SetIntField(This, g_wdayField, 0); - env->SetIntField(This, g_ydayField, 0); - env->SetIntField(This, g_isdstField, -1); - env->SetLongField(This, g_gmtoffField, 0); - - return inUtc; -} - -static jboolean android_text_format_Time_parse3339(JNIEnv* env, - jobject This, - jstring strObj) -{ - jsize len = env->GetStringLength(strObj); - if (len < 10) { - jniThrowException(env, "android/util/TimeFormatException", - "String too short --- expected at least 10 characters."); - return JNI_FALSE; - } - - jboolean inUtc = JNI_FALSE; - - ScopedStringChars s(env, strObj); - - // year - int n; - bool thrown = false; - n = get_char(env, s, 0, 1000, &thrown); - n += get_char(env, s, 1, 100, &thrown); - n += get_char(env, s, 2, 10, &thrown); - n += get_char(env, s, 3, 1, &thrown); - if (thrown) return JNI_FALSE; - env->SetIntField(This, g_yearField, n); - - // - - if (!check_char(env, s, 4, '-')) return JNI_FALSE; - - // month - n = get_char(env, s, 5, 10, &thrown); - n += get_char(env, s, 6, 1, &thrown); - --n; - if (thrown) return JNI_FALSE; - env->SetIntField(This, g_monField, n); - - // - - if (!check_char(env, s, 7, '-')) return JNI_FALSE; - - // day - n = get_char(env, s, 8, 10, &thrown); - n += get_char(env, s, 9, 1, &thrown); - if (thrown) return JNI_FALSE; - env->SetIntField(This, g_mdayField, n); - - if (len >= 19) { - // T - if (!check_char(env, s, 10, 'T')) return JNI_FALSE; - - env->SetBooleanField(This, g_allDayField, JNI_FALSE); - // hour - n = get_char(env, s, 11, 10, &thrown); - n += get_char(env, s, 12, 1, &thrown); - if (thrown) return JNI_FALSE; - int hour = n; - // env->SetIntField(This, g_hourField, n); - - // : - if (!check_char(env, s, 13, ':')) return JNI_FALSE; - - // minute - n = get_char(env, s, 14, 10, &thrown); - n += get_char(env, s, 15, 1, &thrown); - if (thrown) return JNI_FALSE; - int minute = n; - // env->SetIntField(This, g_minField, n); - - // : - if (!check_char(env, s, 16, ':')) return JNI_FALSE; - - // second - n = get_char(env, s, 17, 10, &thrown); - n += get_char(env, s, 18, 1, &thrown); - if (thrown) return JNI_FALSE; - env->SetIntField(This, g_secField, n); - - // skip the '.XYZ' -- we don't care about subsecond precision. - int tz_index = 19; - if (tz_index < len && s[tz_index] == '.') { - do { - tz_index++; - } while (tz_index < len - && s[tz_index] >= '0' - && s[tz_index] <= '9'); - } - - int offset = 0; - if (len > tz_index) { - char c = s[tz_index]; - - // NOTE: the offset is meant to be subtracted to get from local time - // to UTC. we therefore use 1 for '-' and -1 for '+'. - switch (c) { - case 'Z': - // Zulu time -- UTC - offset = 0; - break; - case '-': - offset = 1; - break; - case '+': - offset = -1; - break; - default: - jniThrowExceptionFmt(env, "android/util/TimeFormatException", - "Unexpected character 0x%02x at position %d. Expected + or -", - c, tz_index); - return JNI_FALSE; - } - inUtc = JNI_TRUE; - - if (offset != 0) { - if (len < tz_index + 6) { - jniThrowExceptionFmt(env, "android/util/TimeFormatException", - "Unexpected length; should be %d characters", - tz_index + 6); - return JNI_FALSE; - } - - // hour - n = get_char(env, s, tz_index + 1, 10, &thrown); - n += get_char(env, s, tz_index + 2, 1, &thrown); - if (thrown) return JNI_FALSE; - n *= offset; - hour += n; - - // : - if (!check_char(env, s, tz_index + 3, ':')) return JNI_FALSE; - - // minute - n = get_char(env, s, tz_index + 4, 10, &thrown); - n += get_char(env, s, tz_index + 5, 1, &thrown); - if (thrown) return JNI_FALSE; - n *= offset; - minute += n; - } - } - env->SetIntField(This, g_hourField, hour); - env->SetIntField(This, g_minField, minute); - - if (offset != 0) { - // we need to normalize after applying the hour and minute offsets - android_text_format_Time_normalize(env, This, false /* use isdst */); - // The timezone is set to UTC in the calling Java code. - } - } else { - env->SetBooleanField(This, g_allDayField, JNI_TRUE); - env->SetIntField(This, g_hourField, 0); - env->SetIntField(This, g_minField, 0); - env->SetIntField(This, g_secField, 0); - } - - env->SetIntField(This, g_wdayField, 0); - env->SetIntField(This, g_ydayField, 0); - env->SetIntField(This, g_isdstField, -1); - env->SetLongField(This, g_gmtoffField, 0); - - return inUtc; -} - -// ============================================================================ -/* - * JNI registration. - */ -static JNINativeMethod gMethods[] = { - /* name, signature, funcPtr */ - { "normalize", "(Z)J", (void*)android_text_format_Time_normalize }, - { "switchTimezone", "(Ljava/lang/String;)V", (void*)android_text_format_Time_switchTimezone }, - { "nativeCompare", "(Landroid/text/format/Time;Landroid/text/format/Time;)I", (void*)android_text_format_Time_compare }, - { "format1", "(Ljava/lang/String;)Ljava/lang/String;", (void*)android_text_format_Time_format }, - { "format2445", "()Ljava/lang/String;", (void*)android_text_format_Time_format2445 }, - { "toString", "()Ljava/lang/String;", (void*)android_text_format_Time_toString }, - { "nativeParse", "(Ljava/lang/String;)Z", (void*)android_text_format_Time_parse }, - { "nativeParse3339", "(Ljava/lang/String;)Z", (void*)android_text_format_Time_parse3339 }, - { "setToNow", "()V", (void*)android_text_format_Time_setToNow }, - { "toMillis", "(Z)J", (void*)android_text_format_Time_toMillis }, - { "set", "(J)V", (void*)android_text_format_Time_set } -}; - -int register_android_text_format_Time(JNIEnv* env) -{ - jclass timeClass = env->FindClass("android/text/format/Time"); - - g_timeClass = (jclass) env->NewGlobalRef(timeClass); - - g_allDayField = env->GetFieldID(timeClass, "allDay", "Z"); - g_secField = env->GetFieldID(timeClass, "second", "I"); - g_minField = env->GetFieldID(timeClass, "minute", "I"); - g_hourField = env->GetFieldID(timeClass, "hour", "I"); - g_mdayField = env->GetFieldID(timeClass, "monthDay", "I"); - g_monField = env->GetFieldID(timeClass, "month", "I"); - g_yearField = env->GetFieldID(timeClass, "year", "I"); - g_wdayField = env->GetFieldID(timeClass, "weekDay", "I"); - g_ydayField = env->GetFieldID(timeClass, "yearDay", "I"); - g_isdstField = env->GetFieldID(timeClass, "isDst", "I"); - g_gmtoffField = env->GetFieldID(timeClass, "gmtoff", "J"); - g_timezoneField = env->GetFieldID(timeClass, "timezone", "Ljava/lang/String;"); - - g_shortMonthsField = env->GetStaticFieldID(timeClass, "sShortMonths", "[Ljava/lang/String;"); - g_longMonthsField = env->GetStaticFieldID(timeClass, "sLongMonths", "[Ljava/lang/String;"); - g_longStandaloneMonthsField = env->GetStaticFieldID(timeClass, "sLongStandaloneMonths", "[Ljava/lang/String;"); - g_shortWeekdaysField = env->GetStaticFieldID(timeClass, "sShortWeekdays", "[Ljava/lang/String;"); - g_longWeekdaysField = env->GetStaticFieldID(timeClass, "sLongWeekdays", "[Ljava/lang/String;"); - g_timeOnlyFormatField = env->GetStaticFieldID(timeClass, "sTimeOnlyFormat", "Ljava/lang/String;"); - g_dateOnlyFormatField = env->GetStaticFieldID(timeClass, "sDateOnlyFormat", "Ljava/lang/String;"); - g_dateTimeFormatField = env->GetStaticFieldID(timeClass, "sDateTimeFormat", "Ljava/lang/String;"); - g_amField = env->GetStaticFieldID(timeClass, "sAm", "Ljava/lang/String;"); - g_pmField = env->GetStaticFieldID(timeClass, "sPm", "Ljava/lang/String;"); - g_dateCommandField = env->GetStaticFieldID(timeClass, "sDateCommand", "Ljava/lang/String;"); - g_localeField = env->GetStaticFieldID(timeClass, "sLocale", "Ljava/util/Locale;"); - - return AndroidRuntime::registerNativeMethods(env, "android/text/format/Time", gMethods, NELEM(gMethods)); -} - -}; // namespace android diff --git a/core/res/res/values-mcc214-mnc07/config.xml b/core/res/res/values-mcc214-mnc07/config.xml new file mode 100644 index 0000000..ce7526c --- /dev/null +++ b/core/res/res/values-mcc214-mnc07/config.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2013, 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 my 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. +*/ +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <!-- Array of ConnectivityManager.TYPE_xxxx values allowable for tethering --> + <!-- Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or + <!== [0,1,5,7] for TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI and TYPE_BLUETOOTH --> + <integer-array translatable="false" name="config_tether_upstream_types"> + <item>1</item> + <item>4</item> + <item>7</item> + <item>9</item> + </integer-array> + + <!-- String containing the apn value for tethering. May be overriden by secure settings + TETHER_DUN_APN. Value is a comma separated series of strings: + "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type" + note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" --> + <string translatable="false" name="config_tether_apndata">Conexion Compartida,movistar.es,,,MOVISTAR,MOVISTAR,,,,,214,07,1,DUN</string> + +</resources> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 538003f..cc2a298 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1387,7 +1387,7 @@ <string name="permlab_accessLocationExtraCommands">access extra location provider commands</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_accessLocationExtraCommands">Allows the app to access - extra location provider commands. This may allow the app to to interfere + extra location provider commands. This may allow the app to interfere with the operation of the GPS or other location sources.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/Android.mk new file mode 100644 index 0000000..9f04228 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/Android.mk @@ -0,0 +1,41 @@ +# Copyright (C) 2014 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. + +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_SDK_VERSION := 9 + +LOCAL_PACKAGE_NAME := MultiDexLegacyVersionedTestApp_v1 + +LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex + +mainDexList:= \ + $(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list + +LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(mainDexList) --minimal-main-dex + +include $(BUILD_PACKAGE) + +$(mainDexList): $(full_classes_proguard_jar) | $(HOST_OUT_EXECUTABLES)/mainDexClasses + $(HOST_OUT_EXECUTABLES)/mainDexClasses $< 1>$@ + echo "com/android/framework/multidexlegacyversionedtestapp/MultiDexUpdateTest.class" >> $@ + +$(built_dex_intermediate): $(mainDexList) + diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/AndroidManifest.xml b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/AndroidManifest.xml new file mode 100644 index 0000000..c7b066d --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/AndroidManifest.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.framework.multidexlegacyversionedtestapp" + android:versionCode="1" + android:versionName="1.0" > + + <uses-sdk + android:minSdkVersion="9" + android:targetSdkVersion="18" /> + + <application + android:name="android.support.multidex.MultiDexApplication" + android:allowBackup="true" + android:label="MultiDexLegacyVersionedTestApp_v1"> + <activity + android:name="com.android.framework.multidexlegacyversionedtestapp.MainActivity" + android:label="MultiDexLegacyVersionedTestApp_v1" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="android.test.InstrumentationTestRunner" + android:targetPackage="com.android.framework.multidexlegacyversionedtestapp" + android:label="Test for MultiDexLegacyVersionedTestApp_v1" /> + +</manifest> diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/res/layout/activity_main.xml b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/res/layout/activity_main.xml new file mode 100644 index 0000000..58ae67a --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/res/layout/activity_main.xml @@ -0,0 +1,7 @@ +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".MainActivity" > + +</RelativeLayout> diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/src/com/android/framework/multidexlegacyversionedtestapp/ClassForMainDex.java b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/src/com/android/framework/multidexlegacyversionedtestapp/ClassForMainDex.java new file mode 100644 index 0000000..8662562 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/src/com/android/framework/multidexlegacyversionedtestapp/ClassForMainDex.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2014 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.framework.multidexlegacyversionedtestapp; + +/** + * Class directly referenced from Activity, will be kept in main dex. The class is not referenced + * by <clinit> or <init>, its direct references are not kept in main dex. + */ +public class ClassForMainDex { + + public static int getVersion() { + return Version.getVersion(); + } + +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/src/com/android/framework/multidexlegacyversionedtestapp/MainActivity.java b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/src/com/android/framework/multidexlegacyversionedtestapp/MainActivity.java new file mode 100644 index 0000000..351d860 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/src/com/android/framework/multidexlegacyversionedtestapp/MainActivity.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2014 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.framework.multidexlegacyversionedtestapp; + +import android.app.Activity; +import android.os.Bundle; + +public class MainActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + } + + public int getVersion() { + return ClassForMainDex.getVersion(); + } + +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/src/com/android/framework/multidexlegacyversionedtestapp/MultiDexUpdateTest.java b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/src/com/android/framework/multidexlegacyversionedtestapp/MultiDexUpdateTest.java new file mode 100644 index 0000000..24b4d69 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/src/com/android/framework/multidexlegacyversionedtestapp/MultiDexUpdateTest.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2014 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.framework.multidexlegacyversionedtestapp; + +import android.test.ActivityInstrumentationTestCase2; + +/** + * Run the tests with: <code>adb shell am instrument -w + com.android.framework.multidexlegacyversionedtestapp/android.test.InstrumentationTestRunner +</code> + */ +public class MultiDexUpdateTest extends ActivityInstrumentationTestCase2<MainActivity> +{ + public MultiDexUpdateTest() { + super(MainActivity.class); + } + + /** + * Tests that all classes of the application can be loaded. Verifies also that we load the + * correct version of {@link Version} ie the class is the secondary dex file. + */ + public void testAllClassAvailable() + { + assertEquals(1, getActivity().getVersion()); + } +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/src/com/android/framework/multidexlegacyversionedtestapp/Version.java b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/src/com/android/framework/multidexlegacyversionedtestapp/Version.java new file mode 100644 index 0000000..eb9827a --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/src/com/android/framework/multidexlegacyversionedtestapp/Version.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2014 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.framework.multidexlegacyversionedtestapp; + +/* can go in secondary dex */ +public class Version { + + public static int getVersion() { + return 1; + } +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/Android.mk new file mode 100644 index 0000000..1b8da41 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/Android.mk @@ -0,0 +1,41 @@ +# Copyright (C) 2014 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. + +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_SDK_VERSION := 9 + +LOCAL_PACKAGE_NAME := MultiDexLegacyVersionedTestApp_v2 + +LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex + +mainDexList:= \ + $(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list + +LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(mainDexList) --minimal-main-dex + +include $(BUILD_PACKAGE) + +$(mainDexList): $(full_classes_proguard_jar) | $(HOST_OUT_EXECUTABLES)/mainDexClasses + $(HOST_OUT_EXECUTABLES)/mainDexClasses $< 1>$@ + echo "com/android/framework/multidexlegacyversionedtestapp/MultiDexUpdateTest.class" >> $@ + +$(built_dex_intermediate): $(mainDexList) + diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/AndroidManifest.xml b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/AndroidManifest.xml new file mode 100644 index 0000000..4d24793 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/AndroidManifest.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.framework.multidexlegacyversionedtestapp" + android:versionCode="2" + android:versionName="2.0" > + + <uses-sdk + android:minSdkVersion="9" + android:targetSdkVersion="18" /> + + <application + android:name="android.support.multidex.MultiDexApplication" + android:allowBackup="true" + android:label="MultiDexLegacyVersionedTestApp_v2"> + <activity + android:name="com.android.framework.multidexlegacyversionedtestapp.MainActivity" + android:label="MultiDexLegacyVersionedTestApp_v2" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="android.test.InstrumentationTestRunner" + android:targetPackage="com.android.framework.multidexlegacyversionedtestapp" + android:label="Test for MultiDexLegacyVersionedTestApp_v2" /> + +</manifest> diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/res/layout/activity_main.xml b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/res/layout/activity_main.xml new file mode 100644 index 0000000..58ae67a --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/res/layout/activity_main.xml @@ -0,0 +1,7 @@ +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".MainActivity" > + +</RelativeLayout> diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/src/com/android/framework/multidexlegacyversionedtestapp/ClassForMainDex.java b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/src/com/android/framework/multidexlegacyversionedtestapp/ClassForMainDex.java new file mode 100644 index 0000000..8662562 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/src/com/android/framework/multidexlegacyversionedtestapp/ClassForMainDex.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2014 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.framework.multidexlegacyversionedtestapp; + +/** + * Class directly referenced from Activity, will be kept in main dex. The class is not referenced + * by <clinit> or <init>, its direct references are not kept in main dex. + */ +public class ClassForMainDex { + + public static int getVersion() { + return Version.getVersion(); + } + +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/src/com/android/framework/multidexlegacyversionedtestapp/MainActivity.java b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/src/com/android/framework/multidexlegacyversionedtestapp/MainActivity.java new file mode 100644 index 0000000..351d860 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/src/com/android/framework/multidexlegacyversionedtestapp/MainActivity.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2014 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.framework.multidexlegacyversionedtestapp; + +import android.app.Activity; +import android.os.Bundle; + +public class MainActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + } + + public int getVersion() { + return ClassForMainDex.getVersion(); + } + +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/src/com/android/framework/multidexlegacyversionedtestapp/MultiDexUpdateTest.java b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/src/com/android/framework/multidexlegacyversionedtestapp/MultiDexUpdateTest.java new file mode 100644 index 0000000..f130cb2 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/src/com/android/framework/multidexlegacyversionedtestapp/MultiDexUpdateTest.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2014 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.framework.multidexlegacyversionedtestapp; + +import android.test.ActivityInstrumentationTestCase2; + +/** + * Run the tests with: <code>adb shell am instrument -w + com.android.framework.multidexlegacyversionedtestapp/android.test.InstrumentationTestRunner +</code> + */ +public class MultiDexUpdateTest extends ActivityInstrumentationTestCase2<MainActivity> +{ + public MultiDexUpdateTest() { + super(MainActivity.class); + } + + /** + * Tests that all classes of the application can be loaded. Verifies also that we load the + * correct version of {@link Version} ie the class is the secondary dex file. + */ + public void testAllClassAvailable() + { + assertEquals(2, getActivity().getVersion()); + } +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/src/com/android/framework/multidexlegacyversionedtestapp/Version.java b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/src/com/android/framework/multidexlegacyversionedtestapp/Version.java new file mode 100644 index 0000000..1f2305f --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/src/com/android/framework/multidexlegacyversionedtestapp/Version.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2014 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.framework.multidexlegacyversionedtestapp; + +/* can go in secondary dex */ +public class Version { + + public static int getVersion() { + return 2; + } +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/Android.mk new file mode 100644 index 0000000..945bfcc --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/Android.mk @@ -0,0 +1,41 @@ +# Copyright (C) 2014 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. + +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_SDK_VERSION := 9 + +LOCAL_PACKAGE_NAME := MultiDexLegacyVersionedTestApp_v3 + +LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex + +mainDexList:= \ + $(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list + +LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(mainDexList) --minimal-main-dex + +include $(BUILD_PACKAGE) + +$(mainDexList): $(full_classes_proguard_jar) | $(HOST_OUT_EXECUTABLES)/mainDexClasses + $(HOST_OUT_EXECUTABLES)/mainDexClasses $< 1>$@ + echo "com/android/framework/multidexlegacyversionedtestapp/MultiDexUpdateTest.class" >> $@ + +$(built_dex_intermediate): $(mainDexList) + diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/AndroidManifest.xml b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/AndroidManifest.xml new file mode 100644 index 0000000..76c92dd --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/AndroidManifest.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.framework.multidexlegacyversionedtestapp" + android:versionCode="3" + android:versionName="3.0" > + + <uses-sdk + android:minSdkVersion="9" + android:targetSdkVersion="18" /> + + <application + android:name="android.support.multidex.MultiDexApplication" + android:allowBackup="true" + android:label="MultiDexLegacyVersionedTestApp_v3"> + <activity + android:name="com.android.framework.multidexlegacyversionedtestapp.MainActivity" + android:label="MultiDexLegacyVersionedTestApp_v3" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="android.test.InstrumentationTestRunner" + android:targetPackage="com.android.framework.multidexlegacyversionedtestapp" + android:label="Test for MultiDexLegacyVersionedTestApp_v3" /> + +</manifest> diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/res/layout/activity_main.xml b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/res/layout/activity_main.xml new file mode 100644 index 0000000..58ae67a --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/res/layout/activity_main.xml @@ -0,0 +1,7 @@ +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".MainActivity" > + +</RelativeLayout> diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/src/com/android/framework/multidexlegacyversionedtestapp/ClassForMainDex.java b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/src/com/android/framework/multidexlegacyversionedtestapp/ClassForMainDex.java new file mode 100644 index 0000000..8662562 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/src/com/android/framework/multidexlegacyversionedtestapp/ClassForMainDex.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2014 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.framework.multidexlegacyversionedtestapp; + +/** + * Class directly referenced from Activity, will be kept in main dex. The class is not referenced + * by <clinit> or <init>, its direct references are not kept in main dex. + */ +public class ClassForMainDex { + + public static int getVersion() { + return Version.getVersion(); + } + +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/src/com/android/framework/multidexlegacyversionedtestapp/MainActivity.java b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/src/com/android/framework/multidexlegacyversionedtestapp/MainActivity.java new file mode 100644 index 0000000..351d860 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/src/com/android/framework/multidexlegacyversionedtestapp/MainActivity.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2014 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.framework.multidexlegacyversionedtestapp; + +import android.app.Activity; +import android.os.Bundle; + +public class MainActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + } + + public int getVersion() { + return ClassForMainDex.getVersion(); + } + +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/src/com/android/framework/multidexlegacyversionedtestapp/MultiDexUpdateTest.java b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/src/com/android/framework/multidexlegacyversionedtestapp/MultiDexUpdateTest.java new file mode 100644 index 0000000..67aa478 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/src/com/android/framework/multidexlegacyversionedtestapp/MultiDexUpdateTest.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2014 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.framework.multidexlegacyversionedtestapp; + +import android.test.ActivityInstrumentationTestCase2; + +/** + * Run the tests with: <code>adb shell am instrument -w + com.android.framework.multidexlegacyversionedtestapp/android.test.InstrumentationTestRunner +</code> + */ +public class MultiDexUpdateTest extends ActivityInstrumentationTestCase2<MainActivity> +{ + public MultiDexUpdateTest() { + super(MainActivity.class); + } + + /** + * Tests that all classes of the application can be loaded. Verifies also that we load the + * correct version of {@link Version} ie the class is the secondary dex file. + */ + public void testAllClassAvailable() + { + assertEquals(3, getActivity().getVersion()); + } +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/src/com/android/framework/multidexlegacyversionedtestapp/Version.java b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/src/com/android/framework/multidexlegacyversionedtestapp/Version.java new file mode 100644 index 0000000..1c8ef3b --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/src/com/android/framework/multidexlegacyversionedtestapp/Version.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2014 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.framework.multidexlegacyversionedtestapp; + +/* can go in secondary dex */ +public class Version { + + public static int getVersion() { + return 3; + } +} diff --git a/docs/html/guide/topics/connectivity/nfc/nfc.jd b/docs/html/guide/topics/connectivity/nfc/nfc.jd index 5011872..19ce4d7 100644 --- a/docs/html/guide/topics/connectivity/nfc/nfc.jd +++ b/docs/html/guide/topics/connectivity/nfc/nfc.jd @@ -477,7 +477,7 @@ tag from the intent. Intents can contain the following extras depending on the t <li>{@link android.nfc.NfcAdapter#EXTRA_TAG} (required): A {@link android.nfc.Tag} object representing the scanned tag.</li> <li>{@link android.nfc.NfcAdapter#EXTRA_NDEF_MESSAGES} (optional): An array of NDEF messages -parsed from the tag. This extra is mandatory on {@link android.nfc.NfcAdapter#ACTION_NDEF_DISCOVERED +parsed from the tag. This extra is mandatory on {@link android.nfc.NfcAdapter#ACTION_NDEF_DISCOVERED} intents.</li> <li>{@link android.nfc.NfcAdapter#EXTRA_ID} (optional): The low-level ID of the tag.</li></ul> diff --git a/docs/html/guide/topics/manifest/application-element.jd b/docs/html/guide/topics/manifest/application-element.jd index 28deed9..33f6bce 100644 --- a/docs/html/guide/topics/manifest/application-element.jd +++ b/docs/html/guide/topics/manifest/application-element.jd @@ -81,7 +81,7 @@ information. </p></dd> -<dt><a name="allowbackup"></a>{@code android:allowbackup}</dt> +<dt><a name="allowbackup"></a>{@code android:allowBackup}</dt> <dd>Whether to allow the application to participate in the backup and restore infrastructure. If this attribute is set to false, no backup or restore of the application will ever be performed, even by a full-system diff --git a/docs/html/guide/topics/ui/layout/relative.jd b/docs/html/guide/topics/ui/layout/relative.jd index 65c5617..1acb2e3 100644 --- a/docs/html/guide/topics/ui/layout/relative.jd +++ b/docs/html/guide/topics/ui/layout/relative.jd @@ -20,7 +20,7 @@ page.tags="relativelayout" <p>{@link android.widget.RelativeLayout} is a view group that displays child views in relative positions. The position of each view can be specified as relative to sibling elements (such as to the left-of or below another view) or in positions relative to the parent {@link -android.widget.RelativeLayout} area (such as aligned to the bottom, left of center).</p> +android.widget.RelativeLayout} area (such as aligned to the bottom, left or center).</p> <img src="{@docRoot}images/ui/relativelayout.png" alt="" /> diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java index 67e8f23..db2ad9d 100644 --- a/graphics/java/android/graphics/BitmapFactory.java +++ b/graphics/java/android/graphics/BitmapFactory.java @@ -472,7 +472,7 @@ public class BitmapFactory { /** * Synonym for {@link #decodeResource(Resources, int, android.graphics.BitmapFactory.Options)} - * will null Options. + * with null Options. * * @param res The resources object containing the image data * @param id The resource id of the image data diff --git a/graphics/java/android/graphics/PathMeasure.java b/graphics/java/android/graphics/PathMeasure.java index e56716f..91b092a 100644 --- a/graphics/java/android/graphics/PathMeasure.java +++ b/graphics/java/android/graphics/PathMeasure.java @@ -35,7 +35,7 @@ public class PathMeasure { /** * Create a PathMeasure object associated with the specified path object - * (already created and specified). The meansure object can now return the + * (already created and specified). The measure object can now return the * path's length, and the position and tangent of any position along the * path. * diff --git a/include/android_runtime/AndroidRuntime.h b/include/android_runtime/AndroidRuntime.h index 3dfdb46..45aa745 100644 --- a/include/android_runtime/AndroidRuntime.h +++ b/include/android_runtime/AndroidRuntime.h @@ -116,6 +116,14 @@ public: private: static int startReg(JNIEnv* env); + bool parseRuntimeOption(const char* property, + char* buffer, + const char* runtimeArg, + const char* defaultArg = ""); + bool parseCompilerRuntimeOption(const char* property, + char* buffer, + const char* runtimeArg, + const char* quotingArg); void parseExtraOpts(char* extraOptsBuf, const char* quotingArg); int startVm(JavaVM** pJavaVM, JNIEnv** pEnv); diff --git a/libs/androidfw/Android.mk b/libs/androidfw/Android.mk index d21197e..258a296 100644 --- a/libs/androidfw/Android.mk +++ b/libs/androidfw/Android.mk @@ -75,7 +75,6 @@ LOCAL_SHARED_LIBRARIES := \ LOCAL_STATIC_LIBRARIES := libziparchive LOCAL_C_INCLUDES := \ - external/icu4c/common \ external/zlib \ system/core/include diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp index 87164ca..56c95bd 100644 --- a/libs/androidfw/AssetManager.cpp +++ b/libs/androidfw/AssetManager.cpp @@ -347,6 +347,15 @@ void AssetManager::setLocale(const char* locale) setLocaleLocked(locale); } + +static const char kFilPrefix[] = "fil"; +static const char kTlPrefix[] = "tl"; + +// The sizes of the prefixes, excluding the 0 suffix. +// char. +static const int kFilPrefixLen = sizeof(kFilPrefix) - 1; +static const int kTlPrefixLen = sizeof(kTlPrefix) - 1; + void AssetManager::setLocaleLocked(const char* locale) { if (mLocale != NULL) { @@ -355,8 +364,46 @@ void AssetManager::setLocaleLocked(const char* locale) //mZipSet.purgeLocale(); delete[] mLocale; } - mLocale = strdupNew(locale); + // If we're attempting to set a locale that starts with "fil", + // we should convert it to "tl" for backwards compatibility since + // we've been using "tl" instead of "fil" prior to L. + // + // If the resource table already has entries for "fil", we use that + // instead of attempting a fallback. + if (strncmp(locale, kFilPrefix, kFilPrefixLen) == 0) { + Vector<String8> locales; + ResTable* res = mResources; + if (res != NULL) { + res->getLocales(&locales); + } + const size_t localesSize = locales.size(); + bool hasFil = false; + for (size_t i = 0; i < localesSize; ++i) { + if (locales[i].find(kFilPrefix) == 0) { + hasFil = true; + break; + } + } + + + if (!hasFil) { + const size_t newLocaleLen = strlen(locale); + // This isn't a bug. We really do want mLocale to be 1 byte + // shorter than locale, because we're replacing "fil-" with + // "tl-". + mLocale = new char[newLocaleLen]; + // Copy over "tl". + memcpy(mLocale, kTlPrefix, kTlPrefixLen); + // Copy the rest of |locale|, including the terminating '\0'. + memcpy(mLocale + kTlPrefixLen, locale + kFilPrefixLen, + newLocaleLen - kFilPrefixLen + 1); + updateResourceParamsLocked(); + return; + } + } + + mLocale = strdupNew(locale); updateResourceParamsLocked(); } @@ -741,6 +788,16 @@ void AssetManager::getLocales(Vector<String8>* locales) const if (res != NULL) { res->getLocales(locales); } + + const size_t numLocales = locales->size(); + for (size_t i = 0; i < numLocales; ++i) { + const String8& localeStr = locales->itemAt(i); + if (localeStr.find(kTlPrefix) == 0) { + String8 replaced("fil"); + replaced += (localeStr.string() + kTlPrefixLen); + locales->editItemAt(i) = replaced; + } + } } /* diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index f0adea9..ebcd1c6 100755 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -283,21 +283,34 @@ void OpenGLRenderer::syncState() { } } -void OpenGLRenderer::startTiling(const sp<Snapshot>& s, bool opaque) { +void OpenGLRenderer::startTiling(const sp<Snapshot>& s, bool opaque, bool expand) { if (!mSuppressTiling) { Rect* clip = &mTilingClip; if (s->flags & Snapshot::kFlagFboTarget) { clip = &(s->layer->clipRect); } - startTiling(*clip, s->height, opaque); + startTiling(*clip, s->height, opaque, expand); } } -void OpenGLRenderer::startTiling(const Rect& clip, int windowHeight, bool opaque) { +void OpenGLRenderer::startTiling(const Rect& clip, int windowHeight, bool opaque, bool expand) { if (!mSuppressTiling) { - mCaches.startTiling(clip.left, windowHeight - clip.bottom, + if(expand) { + // Expand the startTiling region by 1 + int leftNotZero = (clip.left > 0) ? 1 : 0; + int topNotZero = (windowHeight - clip.bottom > 0) ? 1 : 0; + + mCaches.startTiling( + clip.left - leftNotZero, + windowHeight - clip.bottom - topNotZero, + clip.right - clip.left + leftNotZero + 1, + clip.bottom - clip.top + topNotZero + 1, + opaque); + } else { + mCaches.startTiling(clip.left, windowHeight - clip.bottom, clip.right - clip.left, clip.bottom - clip.top, opaque); + } } } @@ -1003,7 +1016,8 @@ bool OpenGLRenderer::createFboLayer(Layer* layer, Rect& bounds, Rect& clip, GLui glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, layer->getTexture(), 0); - startTiling(mSnapshot, true); + // Expand the startTiling region by 1 + startTiling(mSnapshot, true, true); // Clear the FBO, expand the clear region by 1 to get nice bilinear filtering mCaches.enableScissor(); diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h index 9afb7ad..2e03a1b 100644..100755 --- a/libs/hwui/OpenGLRenderer.h +++ b/libs/hwui/OpenGLRenderer.h @@ -587,14 +587,14 @@ private: * This method needs to be invoked every time getTargetFbo() is * bound again. */ - void startTiling(const sp<Snapshot>& snapshot, bool opaque = false); + void startTiling(const sp<Snapshot>& snapshot, bool opaque = false, bool expand = false); /** * Tells the GPU what part of the screen is about to be redrawn. * This method needs to be invoked every time getTargetFbo() is * bound again. */ - void startTiling(const Rect& clip, int windowHeight, bool opaque = false); + void startTiling(const Rect& clip, int windowHeight, bool opaque = false, bool expand = false); /** * Tells the GPU that we are done drawing the frame or that we diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index ccb4304..170ec98 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -1642,7 +1642,7 @@ public class LocationManager { } if (!intent.isTargetedToPackage()) { IllegalArgumentException e = new IllegalArgumentException( - "pending intent msut be targeted to package"); + "pending intent must be targeted to package"); if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.JELLY_BEAN) { throw e; } else { diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java index 06a8f4c..4599bd6 100644 --- a/media/java/android/media/MediaScanner.java +++ b/media/java/android/media/MediaScanner.java @@ -1184,6 +1184,7 @@ public class MediaScanner HashSet<String> existingFiles = new HashSet<String>(); String directory = "/sdcard/DCIM/.thumbnails"; String [] files = (new File(directory)).list(); + Cursor c = null; if (files == null) files = new String[0]; @@ -1193,7 +1194,7 @@ public class MediaScanner } try { - Cursor c = mMediaProvider.query( + c = mMediaProvider.query( mPackageName, mThumbsUri, new String [] { "_data" }, @@ -1218,11 +1219,12 @@ public class MediaScanner } Log.v(TAG, "/pruneDeadThumbnailFiles... " + c); + } catch (RemoteException e) { + // We will soon be killed... + } finally { if (c != null) { c.close(); } - } catch (RemoteException e) { - // We will soon be killed... } } diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java index ec87c6e..16a0d35 100644 --- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java +++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java @@ -49,6 +49,7 @@ import android.util.Slog; import com.android.internal.app.IMediaContainerService; import com.android.internal.content.NativeLibraryHelper; +import com.android.internal.content.NativeLibraryHelper.ApkHandle; import com.android.internal.content.PackageHelper; import java.io.BufferedInputStream; @@ -106,8 +107,27 @@ public class DefaultContainerService extends IntentService { return null; } - return copyResourceInner(packageURI, cid, key, resFileName, publicResFileName, - isExternal, isForwardLocked, abiOverride); + + if (isExternal) { + // Make sure the sdcard is mounted. + String status = Environment.getExternalStorageState(); + if (!status.equals(Environment.MEDIA_MOUNTED)) { + Slog.w(TAG, "Make sure sdcard is mounted."); + return null; + } + } + + ApkHandle handle = null; + try { + handle = ApkHandle.create(packageURI.getPath()); + return copyResourceInner(packageURI, cid, key, resFileName, publicResFileName, + isExternal, isForwardLocked, handle, abiOverride); + } catch (IOException ioe) { + Slog.w(TAG, "Problem opening APK: " + packageURI.getPath()); + return null; + } finally { + IoUtils.closeQuietly(handle); + } } /** @@ -328,21 +348,11 @@ public class DefaultContainerService extends IntentService { private String copyResourceInner(Uri packageURI, String newCid, String key, String resFileName, String publicResFileName, boolean isExternal, boolean isForwardLocked, - String abiOverride) { - - if (isExternal) { - // Make sure the sdcard is mounted. - String status = Environment.getExternalStorageState(); - if (!status.equals(Environment.MEDIA_MOUNTED)) { - Slog.w(TAG, "Make sure sdcard is mounted."); - return null; - } - } - + ApkHandle handle, String abiOverride) { // The .apk file String codePath = packageURI.getPath(); File codeFile = new File(codePath); - NativeLibraryHelper.ApkHandle handle = new NativeLibraryHelper.ApkHandle(codePath); + String[] abiList = Build.SUPPORTED_ABIS; if (abiOverride != null) { abiList = new String[] { abiOverride }; @@ -849,14 +859,14 @@ public class DefaultContainerService extends IntentService { private int calculateContainerSize(File apkFile, boolean forwardLocked, String abiOverride) throws IOException { - NativeLibraryHelper.ApkHandle handle = new NativeLibraryHelper.ApkHandle(apkFile); - final int abi = NativeLibraryHelper.findSupportedAbi(handle, - (abiOverride != null) ? new String[] { abiOverride } : Build.SUPPORTED_ABIS); - + ApkHandle handle = null; try { + handle = ApkHandle.create(apkFile); + final int abi = NativeLibraryHelper.findSupportedAbi(handle, + (abiOverride != null) ? new String[] { abiOverride } : Build.SUPPORTED_ABIS); return calculateContainerSize(handle, apkFile, abi, forwardLocked); } finally { - handle.close(); + IoUtils.closeQuietly(handle); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index bbac4ef..55dd7ce 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -2449,11 +2449,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { String action = intent.getAction(); if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { int flags = CommandQueue.FLAG_EXCLUDE_NONE; - if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { - String reason = intent.getStringExtra("reason"); - if (reason != null && reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) { - flags |= CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL; - } + String reason = intent.getStringExtra("reason"); + if (reason != null && reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) { + flags |= CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL; } animateCollapsePanels(flags); } diff --git a/rs/java/android/renderscript/Allocation.java b/rs/java/android/renderscript/Allocation.java index 20b7ee7..cb5df0a 100644 --- a/rs/java/android/renderscript/Allocation.java +++ b/rs/java/android/renderscript/Allocation.java @@ -518,13 +518,20 @@ public class Allocation extends BaseObj { throw new RSIllegalArgumentException("Array size mismatch, allocation sizeX = " + mCurrentCount + ", array length = " + d.length); } - // FIXME: requires 64-bit path - int i[] = new int[d.length]; - for (int ct=0; ct < d.length; ct++) { - i[ct] = (int)d[ct].getID(mRS); + if (RenderScript.sPointerSize == 8) { + long i[] = new long[d.length * 4]; + for (int ct=0; ct < d.length; ct++) { + i[ct * 4] = d[ct].getID(mRS); + } + copy1DRangeFromUnchecked(0, mCurrentCount, i); + } else { + int i[] = new int[d.length]; + for (int ct=0; ct < d.length; ct++) { + i[ct] = (int)d[ct].getID(mRS); + } + copy1DRangeFromUnchecked(0, mCurrentCount, i); } - copy1DRangeFromUnchecked(0, mCurrentCount, i); Trace.traceEnd(RenderScript.TRACE_TAG); } diff --git a/rs/java/android/renderscript/RenderScript.java b/rs/java/android/renderscript/RenderScript.java index bbe5c49..f2b1380 100644 --- a/rs/java/android/renderscript/RenderScript.java +++ b/rs/java/android/renderscript/RenderScript.java @@ -615,6 +615,29 @@ public class RenderScript { } } + /** + * Multi-input code. + * + */ + + // @hide + native void rsnScriptForEachMultiClipped(long con, long id, int slot, long[] ains, long aout, byte[] params, + int xstart, int xend, int ystart, int yend, int zstart, int zend); + // @hide + native void rsnScriptForEachMultiClipped(long con, long id, int slot, long[] ains, long aout, + int xstart, int xend, int ystart, int yend, int zstart, int zend); + + // @hide + synchronized void nScriptForEachMultiClipped(long id, int slot, long[] ains, long aout, byte[] params, + int xstart, int xend, int ystart, int yend, int zstart, int zend) { + validate(); + if (params == null) { + rsnScriptForEachMultiClipped(mContext, id, slot, ains, aout, xstart, xend, ystart, yend, zstart, zend); + } else { + rsnScriptForEachMultiClipped(mContext, id, slot, ains, aout, params, xstart, xend, ystart, yend, zstart, zend); + } + } + native void rsnScriptInvokeV(long con, long id, int slot, byte[] params); synchronized void nScriptInvokeV(long id, int slot, byte[] params) { validate(); diff --git a/rs/java/android/renderscript/Script.java b/rs/java/android/renderscript/Script.java index 0e46f94..c49ef94 100644 --- a/rs/java/android/renderscript/Script.java +++ b/rs/java/android/renderscript/Script.java @@ -182,6 +182,54 @@ public class Script extends BaseObj { mRS.nScriptForEachClipped(getID(mRS), slot, in_id, out_id, params, sc.xstart, sc.xend, sc.ystart, sc.yend, sc.zstart, sc.zend); } + /** + * Only intended for use by generated reflected code. + * + * @hide + */ + protected void forEach(int slot, Allocation[] ains, Allocation aout, FieldPacker v) { + forEach(slot, ains, aout, v, new LaunchOptions()); + } + + /** + * Only intended for use by generated reflected code. + * + * @hide + */ + protected void forEach(int slot, Allocation[] ains, Allocation aout, FieldPacker v, LaunchOptions sc) { + mRS.validate(); + + for (Allocation ain : ains) { + mRS.validateObject(ain); + } + + mRS.validateObject(aout); + if (ains == null && aout == null) { + throw new RSIllegalArgumentException( + "At least one of ain or aout is required to be non-null."); + } + + if (sc == null) { + forEach(slot, ains, aout, v); + return; + } + + long[] in_ids = new long[ains.length]; + for (int index = 0; index < ains.length; ++index) { + in_ids[index] = ains[index].getID(mRS); + } + + long out_id = 0; + if (aout != null) { + out_id = aout.getID(mRS); + } + byte[] params = null; + if (v != null) { + params = v.getData(); + } + mRS.nScriptForEachMultiClipped(getID(mRS), slot, in_ids, out_id, params, sc.xstart, sc.xend, sc.ystart, sc.yend, sc.zstart, sc.zend); + } + Script(long id, RenderScript rs) { super(id, rs); } @@ -477,4 +525,3 @@ public class Script extends BaseObj { } } - diff --git a/rs/java/android/renderscript/ScriptIntrinsicBlur.java b/rs/java/android/renderscript/ScriptIntrinsicBlur.java index d1a6fed..c153712 100644 --- a/rs/java/android/renderscript/ScriptIntrinsicBlur.java +++ b/rs/java/android/renderscript/ScriptIntrinsicBlur.java @@ -88,7 +88,7 @@ public final class ScriptIntrinsicBlur extends ScriptIntrinsic { * type. */ public void forEach(Allocation aout) { - forEach(0, null, aout, null); + forEach(0, (Allocation) null, aout, null); } /** @@ -109,4 +109,3 @@ public final class ScriptIntrinsicBlur extends ScriptIntrinsic { return createFieldID(1, null); } } - diff --git a/rs/java/android/renderscript/ScriptIntrinsicConvolve3x3.java b/rs/java/android/renderscript/ScriptIntrinsicConvolve3x3.java index 25f3ee8..586930c 100644 --- a/rs/java/android/renderscript/ScriptIntrinsicConvolve3x3.java +++ b/rs/java/android/renderscript/ScriptIntrinsicConvolve3x3.java @@ -108,7 +108,7 @@ public final class ScriptIntrinsicConvolve3x3 extends ScriptIntrinsic { * type. */ public void forEach(Allocation aout) { - forEach(0, null, aout, null); + forEach(0, (Allocation) null, aout, null); } /** @@ -130,4 +130,3 @@ public final class ScriptIntrinsicConvolve3x3 extends ScriptIntrinsic { } } - diff --git a/rs/java/android/renderscript/ScriptIntrinsicConvolve5x5.java b/rs/java/android/renderscript/ScriptIntrinsicConvolve5x5.java index 71ea4cb..aebafc2 100644 --- a/rs/java/android/renderscript/ScriptIntrinsicConvolve5x5.java +++ b/rs/java/android/renderscript/ScriptIntrinsicConvolve5x5.java @@ -109,7 +109,7 @@ public final class ScriptIntrinsicConvolve5x5 extends ScriptIntrinsic { * type. */ public void forEach(Allocation aout) { - forEach(0, null, aout, null); + forEach(0, (Allocation) null, aout, null); } /** @@ -130,4 +130,3 @@ public final class ScriptIntrinsicConvolve5x5 extends ScriptIntrinsic { return createFieldID(1, null); } } - diff --git a/rs/java/android/renderscript/ScriptIntrinsicResize.java b/rs/java/android/renderscript/ScriptIntrinsicResize.java index cc37120..816029f 100644 --- a/rs/java/android/renderscript/ScriptIntrinsicResize.java +++ b/rs/java/android/renderscript/ScriptIntrinsicResize.java @@ -96,7 +96,7 @@ public final class ScriptIntrinsicResize extends ScriptIntrinsic { * @param opt LaunchOptions for clipping */ public void forEach_bicubic(Allocation aout, Script.LaunchOptions opt) { - forEach(0, null, aout, null, opt); + forEach(0, (Allocation) null, aout, null, opt); } /** @@ -110,4 +110,3 @@ public final class ScriptIntrinsicResize extends ScriptIntrinsic { } - diff --git a/rs/java/android/renderscript/ScriptIntrinsicYuvToRGB.java b/rs/java/android/renderscript/ScriptIntrinsicYuvToRGB.java index f942982..e64c911 100644 --- a/rs/java/android/renderscript/ScriptIntrinsicYuvToRGB.java +++ b/rs/java/android/renderscript/ScriptIntrinsicYuvToRGB.java @@ -66,7 +66,7 @@ public final class ScriptIntrinsicYuvToRGB extends ScriptIntrinsic { * type. */ public void forEach(Allocation aout) { - forEach(0, null, aout, null); + forEach(0, (Allocation) null, aout, null); } /** diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp index 5fe631b..7133a21 100644 --- a/rs/jni/android_renderscript_RenderScript.cpp +++ b/rs/jni/android_renderscript_RenderScript.cpp @@ -1154,6 +1154,101 @@ nScriptForEachClippedV(JNIEnv *_env, jobject _this, jlong con, _env->ReleaseByteArrayElements(params, ptr, JNI_ABORT); } +static void +nScriptForEachMultiClipped(JNIEnv *_env, jobject _this, jlong con, + jlong script, jint slot, jlongArray ains, jlong aout, + jint xstart, jint xend, + jint ystart, jint yend, jint zstart, jint zend) +{ + LOG_API("nScriptForEachMultiClipped, con(%p), s(%p), slot(%i)", (RsContext)con, (void *)script, slot); + + jint in_len = _env->GetArrayLength(ains); + jlong* in_ptr = _env->GetLongArrayElements(ains, NULL); + + RsAllocation *in_allocs = NULL; + + if (sizeof(RsAllocation) == sizeof(jlong)) { + in_allocs = (RsAllocation*)in_ptr; + + } else { + // Convert from 64-bit jlong types to the native pointer type. + + in_allocs = new RsAllocation[in_len]; + + for (int index = in_len; --index >= 0;) { + in_allocs[index] = (RsAllocation)in_ptr[index]; + } + } + + RsScriptCall sc; + sc.xStart = xstart; + sc.xEnd = xend; + sc.yStart = ystart; + sc.yEnd = yend; + sc.zStart = zstart; + sc.zEnd = zend; + sc.strategy = RS_FOR_EACH_STRATEGY_DONT_CARE; + sc.arrayStart = 0; + sc.arrayEnd = 0; + + rsScriptForEachMulti((RsContext)con, (RsScript)script, slot, in_allocs, in_len, (RsAllocation)aout, NULL, 0, &sc, sizeof(sc)); + + if (sizeof(RsAllocation) != sizeof(jlong)) { + delete[] in_allocs; + } + + _env->ReleaseLongArrayElements(ains, in_ptr, JNI_ABORT); +} + +static void +nScriptForEachMultiClippedV(JNIEnv *_env, jobject _this, jlong con, + jlong script, jint slot, jlongArray ains, jlong aout, + jbyteArray params, jint xstart, jint xend, + jint ystart, jint yend, jint zstart, jint zend) +{ + LOG_API("nScriptForEachMultiClippedV, con(%p), s(%p), slot(%i)", (RsContext)con, (void *)script, slot); + + jint in_len = _env->GetArrayLength(ains); + jlong* in_ptr = _env->GetLongArrayElements(ains, NULL); + + RsAllocation *in_allocs = NULL; + + if (sizeof(RsAllocation) == sizeof(jlong)) { + in_allocs = (RsAllocation*)in_ptr; + + } else { + // Convert from 64-bit jlong types to the native pointer type. + + in_allocs = new RsAllocation[in_len]; + + for (int index = in_len; --index >= 0;) { + in_allocs[index] = (RsAllocation)in_ptr[index]; + } + } + + jint param_len = _env->GetArrayLength(params); + jbyte* param_ptr = _env->GetByteArrayElements(params, NULL); + + RsScriptCall sc; + sc.xStart = xstart; + sc.xEnd = xend; + sc.yStart = ystart; + sc.yEnd = yend; + sc.zStart = zstart; + sc.zEnd = zend; + sc.strategy = RS_FOR_EACH_STRATEGY_DONT_CARE; + sc.arrayStart = 0; + sc.arrayEnd = 0; + rsScriptForEachMulti((RsContext)con, (RsScript)script, slot, in_allocs, in_len, (RsAllocation)aout, param_ptr, param_len, &sc, sizeof(sc)); + + if (sizeof(RsAllocation) != sizeof(jlong)) { + delete[] in_allocs; + } + + _env->ReleaseLongArrayElements(ains, in_ptr, JNI_ABORT); + _env->ReleaseByteArrayElements(params, param_ptr, JNI_ABORT); +} + // ----------------------------------- static jlong @@ -1669,6 +1764,8 @@ static JNINativeMethod methods[] = { {"rsnScriptForEach", "(JJIJJ[B)V", (void*)nScriptForEachV }, {"rsnScriptForEachClipped", "(JJIJJIIIIII)V", (void*)nScriptForEachClipped }, {"rsnScriptForEachClipped", "(JJIJJ[BIIIIII)V", (void*)nScriptForEachClippedV }, +{"rsnScriptForEachMultiClipped", "(JJI[JJIIIIII)V", (void*)nScriptForEachMultiClipped }, +{"rsnScriptForEachMultiClipped", "(JJI[JJ[BIIIIII)V", (void*)nScriptForEachMultiClippedV }, {"rsnScriptSetVarI", "(JJII)V", (void*)nScriptSetVarI }, {"rsnScriptGetVarI", "(JJI)I", (void*)nScriptGetVarI }, {"rsnScriptSetVarJ", "(JJIJ)V", (void*)nScriptSetVarJ }, diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index baeced7..ae98070 100644..100755 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -2839,6 +2839,7 @@ public final class ActivityManagerService extends ActivityManagerNative app.setPid(startResult.pid); app.usingWrapper = startResult.usingWrapper; app.removed = false; + app.killedByAm = false; synchronized (mPidsSelfLocked) { this.mPidsSelfLocked.put(startResult.pid, app); Message msg = mHandler.obtainMessage(PROC_START_TIMEOUT_MSG); @@ -12446,6 +12447,7 @@ public final class ActivityManagerService extends ActivityManagerNative app.resetPackageList(mProcessStats); app.unlinkDeathRecipient(); app.makeInactive(mProcessStats); + app.waitingToKill = null; app.forcingToForeground = null; app.foregroundServices = false; app.foregroundActivities = false; diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index 4d6727c..75d32e3 100755 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -957,6 +957,14 @@ final class ActivityStack { next.idle = false; next.results = null; next.newIntents = null; + + if (next.isHomeActivity() && next.isNotResolverActivity()) { + ProcessRecord app = next.task.mActivities.get(0).app; + if (app != null && app != mService.mHomeProcess) { + mService.mHomeProcess = app; + } + } + if (next.nowVisible) { // We won't get a call to reportActivityVisibleLocked() so dismiss lockscreen now. mStackSupervisor.dismissKeyguard(); @@ -1873,6 +1881,8 @@ final class ActivityStack { final int numActivities = activities.size(); for (int i = numActivities - 1; i > 0; --i ) { ActivityRecord target = activities.get(i); + if (target.frontOfTask) + break; final int flags = target.info.flags; final boolean finishOnTaskLaunch = @@ -2040,6 +2050,8 @@ final class ActivityStack { // Do not operate on the root Activity. for (int i = numActivities - 1; i > 0; --i) { ActivityRecord target = activities.get(i); + if (target.frontOfTask) + break; final int flags = target.info.flags; boolean finishOnTaskLaunch = (flags & ActivityInfo.FLAG_FINISH_ON_TASK_LAUNCH) != 0; @@ -3370,6 +3382,7 @@ final class ActivityStack { boolean forceStopPackageLocked(String name, boolean doit, boolean evenPersistent, int userId) { boolean didSomething = false; TaskRecord lastTask = null; + ComponentName homeActivity = null; for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities; int numActivities = activities.size(); @@ -3388,6 +3401,14 @@ final class ActivityStack { } return true; } + if (r.isHomeActivity()) { + if (homeActivity != null && homeActivity.equals(r.realActivity)) { + Slog.i(TAG, "Skip force-stop again " + r); + continue; + } else { + homeActivity = r.realActivity; + } + } didSomething = true; Slog.i(TAG, " Force finishing activity " + r); if (samePackage) { diff --git a/services/java/com/android/server/am/ActivityStackSupervisor.java b/services/java/com/android/server/am/ActivityStackSupervisor.java index d616f1b..cc9df76 100644 --- a/services/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/java/com/android/server/am/ActivityStackSupervisor.java @@ -1709,7 +1709,7 @@ public final class ActivityStackSupervisor { TaskRecord sourceTask = sourceRecord.task; targetStack = sourceTask.stack; moveHomeStack(targetStack.isHomeStack()); - mWindowManager.moveTaskToTop(sourceTask.taskId); + mWindowManager.moveTaskToTop(targetStack.topTask().taskId); if (!addingToTask && (launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) { // In this case, we are adding the activity to an existing diff --git a/services/java/com/android/server/am/BaseErrorDialog.java b/services/java/com/android/server/am/BaseErrorDialog.java index 6ede8f8..35bdee0 100644..100755 --- a/services/java/com/android/server/am/BaseErrorDialog.java +++ b/services/java/com/android/server/am/BaseErrorDialog.java @@ -27,6 +27,11 @@ import android.view.WindowManager; import android.widget.Button; class BaseErrorDialog extends AlertDialog { + private static final int ENABLE_BUTTONS = 0; + private static final int DISABLE_BUTTONS = 1; + + private boolean mConsuming = true; + public BaseErrorDialog(Context context) { super(context, com.android.internal.R.style.Theme_Dialog_AppError); @@ -41,8 +46,8 @@ class BaseErrorDialog extends AlertDialog { public void onStart() { super.onStart(); - setEnabled(false); - mHandler.sendMessageDelayed(mHandler.obtainMessage(0), 1000); + mHandler.sendEmptyMessage(DISABLE_BUTTONS); + mHandler.sendMessageDelayed(mHandler.obtainMessage(ENABLE_BUTTONS), 1000); } public boolean dispatchKeyEvent(KeyEvent event) { @@ -71,12 +76,12 @@ class BaseErrorDialog extends AlertDialog { private Handler mHandler = new Handler() { public void handleMessage(Message msg) { - if (msg.what == 0) { + if (msg.what == ENABLE_BUTTONS) { mConsuming = false; setEnabled(true); + } else if (msg.what == DISABLE_BUTTONS) { + setEnabled(false); } } }; - - private boolean mConsuming = true; } diff --git a/services/java/com/android/server/am/BroadcastQueue.java b/services/java/com/android/server/am/BroadcastQueue.java index bfb667f..ea15653 100644 --- a/services/java/com/android/server/am/BroadcastQueue.java +++ b/services/java/com/android/server/am/BroadcastQueue.java @@ -415,11 +415,16 @@ public final class BroadcastQueue { Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) throws RemoteException { // Send the intent to the receiver asynchronously using one-way binder calls. - if (app != null && app.thread != null) { - // If we have an app thread, do the call through that so it is - // correctly ordered with other one-way calls. - app.thread.scheduleRegisteredReceiver(receiver, intent, resultCode, - data, extras, ordered, sticky, sendingUser, app.repProcState); + if (app != null) { + if (app.thread != null) { + // If we have an app thread, do the call through that so it is + // correctly ordered with other one-way calls. + app.thread.scheduleRegisteredReceiver(receiver, intent, resultCode, + data, extras, ordered, sticky, sendingUser, app.repProcState); + } else { + // Application has died. Receiver doesn't exist. + throw new RemoteException("app.thread must not be null"); + } } else { receiver.performReceive(intent, resultCode, data, extras, ordered, sticky, sendingUser); @@ -661,6 +666,7 @@ public final class BroadcastQueue { // (local and remote) isn't kept in the mBroadcastHistory. r.resultTo = null; } catch (RemoteException e) { + r.resultTo = null; Slog.w(TAG, "Failure [" + mQueueName + "] sending broadcast result of " + r.intent, e); diff --git a/services/java/com/android/server/location/GpsLocationProvider.java b/services/java/com/android/server/location/GpsLocationProvider.java index 9c76c19..aba2a98 100644 --- a/services/java/com/android/server/location/GpsLocationProvider.java +++ b/services/java/com/android/server/location/GpsLocationProvider.java @@ -53,6 +53,7 @@ import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; +import android.os.SystemProperties; import android.os.UserHandle; import android.os.WorkSource; import android.provider.Settings; @@ -61,6 +62,7 @@ import android.provider.Telephony.Sms.Intents; import android.telephony.SmsMessage; import android.telephony.TelephonyManager; import android.telephony.gsm.GsmCellLocation; +import android.text.TextUtils; import android.util.Log; import android.util.NtpTrustedTime; @@ -84,6 +86,8 @@ import java.util.Date; import java.util.Map.Entry; import java.util.Properties; +import libcore.io.IoUtils; + /** * A GPS implementation of LocationProvider used by LocationManager. * @@ -194,7 +198,9 @@ public class GpsLocationProvider implements LocationProviderInterface { private static final int AGPS_SETID_TYPE_IMSI = 1; private static final int AGPS_SETID_TYPE_MSISDN = 2; - private static final String PROPERTIES_FILE = "/etc/gps.conf"; + private static final String PROPERTIES_FILE_PREFIX = "/etc/gps"; + private static final String PROPERTIES_FILE_SUFFIX = ".conf"; + private static final String DEFAULT_PROPERTIES_FILE = PROPERTIES_FILE_PREFIX + PROPERTIES_FILE_SUFFIX; private static final int GPS_GEOFENCE_UNAVAILABLE = 1<<0L; private static final int GPS_GEOFENCE_AVAILABLE = 1<<1L; @@ -444,6 +450,44 @@ public class GpsLocationProvider implements LocationProviderInterface { return native_is_supported(); } + private boolean loadPropertiesFile(String filename) { + mProperties = new Properties(); + try { + File file = new File(filename); + FileInputStream stream = null; + try { + stream = new FileInputStream(file); + mProperties.load(stream); + } finally { + IoUtils.closeQuietly(stream); + } + + mSuplServerHost = mProperties.getProperty("SUPL_HOST"); + String portString = mProperties.getProperty("SUPL_PORT"); + if (mSuplServerHost != null && portString != null) { + try { + mSuplServerPort = Integer.parseInt(portString); + } catch (NumberFormatException e) { + Log.e(TAG, "unable to parse SUPL_PORT: " + portString); + } + } + + mC2KServerHost = mProperties.getProperty("C2K_HOST"); + portString = mProperties.getProperty("C2K_PORT"); + if (mC2KServerHost != null && portString != null) { + try { + mC2KServerPort = Integer.parseInt(portString); + } catch (NumberFormatException e) { + Log.e(TAG, "unable to parse C2K_PORT: " + portString); + } + } + } catch (IOException e) { + Log.w(TAG, "Could not open GPS configuration file " + filename); + return false; + } + return true; + } + public GpsLocationProvider(Context context, ILocationManager ilocationManager, Looper looper) { mContext = context; @@ -472,34 +516,15 @@ public class GpsLocationProvider implements LocationProviderInterface { mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService( BatteryStats.SERVICE_NAME)); - mProperties = new Properties(); - try { - File file = new File(PROPERTIES_FILE); - FileInputStream stream = new FileInputStream(file); - mProperties.load(stream); - stream.close(); - - mSuplServerHost = mProperties.getProperty("SUPL_HOST"); - String portString = mProperties.getProperty("SUPL_PORT"); - if (mSuplServerHost != null && portString != null) { - try { - mSuplServerPort = Integer.parseInt(portString); - } catch (NumberFormatException e) { - Log.e(TAG, "unable to parse SUPL_PORT: " + portString); - } - } + boolean propertiesLoaded = false; + final String gpsHardware = SystemProperties.get("ro.hardware.gps"); + if (!TextUtils.isEmpty(gpsHardware)) { + final String propFilename = PROPERTIES_FILE_PREFIX + "." + gpsHardware + PROPERTIES_FILE_SUFFIX; + propertiesLoaded = loadPropertiesFile(propFilename); + } - mC2KServerHost = mProperties.getProperty("C2K_HOST"); - portString = mProperties.getProperty("C2K_PORT"); - if (mC2KServerHost != null && portString != null) { - try { - mC2KServerPort = Integer.parseInt(portString); - } catch (NumberFormatException e) { - Log.e(TAG, "unable to parse C2K_PORT: " + portString); - } - } - } catch (IOException e) { - Log.w(TAG, "Could not open GPS configuration file " + PROPERTIES_FILE); + if (!propertiesLoaded) { + loadPropertiesFile(DEFAULT_PROPERTIES_FILE); } // construct handler, listen for events diff --git a/services/java/com/android/server/pm/Installer.java b/services/java/com/android/server/pm/Installer.java index 7981a5b..66615c9 100644 --- a/services/java/com/android/server/pm/Installer.java +++ b/services/java/com/android/server/pm/Installer.java @@ -312,6 +312,13 @@ public final class Installer { return execute(builder.toString()); } + public int createUserConfig(int userId) { + StringBuilder builder = new StringBuilder("mkuserconfig"); + builder.append(' '); + builder.append(userId); + return execute(builder.toString()); + } + public int removeUserDataDirs(int userId) { StringBuilder builder = new StringBuilder("rmuser"); builder.append(' '); diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java index 2d5e357..0941adb 100755 --- a/services/java/com/android/server/pm/PackageManagerService.java +++ b/services/java/com/android/server/pm/PackageManagerService.java @@ -5019,8 +5019,9 @@ public class PackageManagerService extends IPackageManager.Stub { * only for non-system apps and system app upgrades. */ if (pkg.applicationInfo.nativeLibraryDir != null) { - final NativeLibraryHelper.ApkHandle handle = new NativeLibraryHelper.ApkHandle(scanFile); + ApkHandle handle = null; try { + handle = ApkHandle.create(scanFile.getPath()); // Enable gross and lame hacks for apps that are built with old // SDK tools. We must scan their APKs for renderscript bitcode and // not launch them if it's present. Don't bother checking on devices @@ -5135,7 +5136,7 @@ public class PackageManagerService extends IPackageManager.Stub { } catch (IOException ioe) { Slog.e(TAG, "Unable to get canonical file " + ioe.toString()); } finally { - handle.close(); + IoUtils.closeQuietly(handle); } } pkg.mScanPath = path; @@ -8768,10 +8769,11 @@ public class PackageManagerService extends IPackageManager.Stub { nativeLibraryFile.delete(); } - final NativeLibraryHelper.ApkHandle handle = new NativeLibraryHelper.ApkHandle(codeFile); String[] abiList = (abiOverride != null) ? new String[] { abiOverride } : Build.SUPPORTED_ABIS; + ApkHandle handle = null; try { + handle = ApkHandle.create(codeFile); if (Build.SUPPORTED_64_BIT_ABIS.length > 0 && abiOverride == null && NativeLibraryHelper.hasRenderscriptBitcode(handle)) { @@ -8786,7 +8788,7 @@ public class PackageManagerService extends IPackageManager.Stub { Slog.e(TAG, "Copying native libraries failed", e); ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR; } finally { - handle.close(); + IoUtils.closeQuietly(handle); } return ret; @@ -12229,23 +12231,30 @@ public class PackageManagerService extends IPackageManager.Stub { final File newNativeDir = new File(newNativePath); if (!isForwardLocked(pkg) && !isExternal(pkg)) { - // NOTE: We do not report any errors from the APK scan and library - // copy at this point. - NativeLibraryHelper.ApkHandle handle = - new NativeLibraryHelper.ApkHandle(newCodePath); - final int abi = NativeLibraryHelper.findSupportedAbi( - handle, Build.SUPPORTED_ABIS); - if (abi >= 0) { - NativeLibraryHelper.copyNativeBinariesIfNeededLI( - handle, newNativeDir, Build.SUPPORTED_ABIS[abi]); + ApkHandle handle = null; + try { + handle = ApkHandle.create(newCodePath); + final int abi = NativeLibraryHelper.findSupportedAbi( + handle, Build.SUPPORTED_ABIS); + if (abi >= 0) { + NativeLibraryHelper.copyNativeBinariesIfNeededLI( + handle, newNativeDir, Build.SUPPORTED_ABIS[abi]); + } + } catch (IOException ioe) { + Slog.w(TAG, "Unable to extract native libs for package :" + + mp.packageName, ioe); + returnCode = PackageManager.MOVE_FAILED_INTERNAL_ERROR; + } finally { + IoUtils.closeQuietly(handle); } - handle.close(); } final int[] users = sUserManager.getUserIds(); - for (int user : users) { - if (mInstaller.linkNativeLibraryDirectory(pkg.packageName, - newNativePath, user) < 0) { - returnCode = PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE; + if (returnCode == PackageManager.MOVE_SUCCEEDED) { + for (int user : users) { + if (mInstaller.linkNativeLibraryDirectory(pkg.packageName, + newNativePath, user) < 0) { + returnCode = PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE; + } } } @@ -12369,6 +12378,7 @@ public class PackageManagerService extends IPackageManager.Stub { /** Called by UserManagerService */ void createNewUserLILPw(int userHandle, File path) { if (mInstaller != null) { + mInstaller.createUserConfig(userHandle); mSettings.createNewUserLILPw(this, mInstaller, userHandle, path); } } diff --git a/services/java/com/android/server/pm/SELinuxMMAC.java b/services/java/com/android/server/pm/SELinuxMMAC.java index c78249b..81302b9 100644 --- a/services/java/com/android/server/pm/SELinuxMMAC.java +++ b/services/java/com/android/server/pm/SELinuxMMAC.java @@ -346,31 +346,21 @@ public final class SELinuxMMAC { */ public static boolean assignSeinfoValue(PackageParser.Package pkg) { - /* - * Non system installed apps should be treated the same. This - * means that any post-loaded apk will be assigned the default - * tag, if one exists in the policy, else null, without respect - * to the signing key. - */ - if (((pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) || - ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0)) { - - // We just want one of the signatures to match. - for (Signature s : pkg.mSignatures) { - if (s == null) - continue; + // We just want one of the signatures to match. + for (Signature s : pkg.mSignatures) { + if (s == null) + continue; - Policy policy = sSigSeinfo.get(s); - if (policy != null) { - String seinfo = policy.checkPolicy(pkg.packageName); - if (seinfo != null) { - pkg.applicationInfo.seinfo = seinfo; - if (DEBUG_POLICY_INSTALL) - Slog.i(TAG, "package (" + pkg.packageName + - ") labeled with seinfo=" + seinfo); + Policy policy = sSigSeinfo.get(s); + if (policy != null) { + String seinfo = policy.checkPolicy(pkg.packageName); + if (seinfo != null) { + pkg.applicationInfo.seinfo = seinfo; + if (DEBUG_POLICY_INSTALL) + Slog.i(TAG, "package (" + pkg.packageName + + ") labeled with seinfo=" + seinfo); - return true; - } + return true; } } } diff --git a/tools/layoutlib/bridge/src/android/graphics/BlendComposite.java b/tools/layoutlib/bridge/src/android/graphics/BlendComposite.java new file mode 100644 index 0000000..a3ec2cc --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/BlendComposite.java @@ -0,0 +1,761 @@ +/* + * Copyright (C) 2014 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.graphics; + +import java.awt.Composite; +import java.awt.CompositeContext; +import java.awt.RenderingHints; +import java.awt.image.ColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; + +/* + * (non-Javadoc) + * The class is adapted from a demo tool for Blending Modes written by + * Romain Guy (romainguy@android.com). The tool is available at + * http://www.curious-creature.org/2006/09/20/new-blendings-modes-for-java2d/ + */ +public final class BlendComposite implements Composite { + public enum BlendingMode { + NORMAL, + AVERAGE, + MULTIPLY, + SCREEN, + DARKEN, + LIGHTEN, + OVERLAY, + HARD_LIGHT, + SOFT_LIGHT, + DIFFERENCE, + NEGATION, + EXCLUSION, + COLOR_DODGE, + INVERSE_COLOR_DODGE, + SOFT_DODGE, + COLOR_BURN, + INVERSE_COLOR_BURN, + SOFT_BURN, + REFLECT, + GLOW, + FREEZE, + HEAT, + ADD, + SUBTRACT, + STAMP, + RED, + GREEN, + BLUE, + HUE, + SATURATION, + COLOR, + LUMINOSITY + } + + public static final BlendComposite Normal = new BlendComposite(BlendingMode.NORMAL); + public static final BlendComposite Average = new BlendComposite(BlendingMode.AVERAGE); + public static final BlendComposite Multiply = new BlendComposite(BlendingMode.MULTIPLY); + public static final BlendComposite Screen = new BlendComposite(BlendingMode.SCREEN); + public static final BlendComposite Darken = new BlendComposite(BlendingMode.DARKEN); + public static final BlendComposite Lighten = new BlendComposite(BlendingMode.LIGHTEN); + public static final BlendComposite Overlay = new BlendComposite(BlendingMode.OVERLAY); + public static final BlendComposite HardLight = new BlendComposite(BlendingMode.HARD_LIGHT); + public static final BlendComposite SoftLight = new BlendComposite(BlendingMode.SOFT_LIGHT); + public static final BlendComposite Difference = new BlendComposite(BlendingMode.DIFFERENCE); + public static final BlendComposite Negation = new BlendComposite(BlendingMode.NEGATION); + public static final BlendComposite Exclusion = new BlendComposite(BlendingMode.EXCLUSION); + public static final BlendComposite ColorDodge = new BlendComposite(BlendingMode.COLOR_DODGE); + public static final BlendComposite InverseColorDodge = new BlendComposite(BlendingMode.INVERSE_COLOR_DODGE); + public static final BlendComposite SoftDodge = new BlendComposite(BlendingMode.SOFT_DODGE); + public static final BlendComposite ColorBurn = new BlendComposite(BlendingMode.COLOR_BURN); + public static final BlendComposite InverseColorBurn = new BlendComposite(BlendingMode.INVERSE_COLOR_BURN); + public static final BlendComposite SoftBurn = new BlendComposite(BlendingMode.SOFT_BURN); + public static final BlendComposite Reflect = new BlendComposite(BlendingMode.REFLECT); + public static final BlendComposite Glow = new BlendComposite(BlendingMode.GLOW); + public static final BlendComposite Freeze = new BlendComposite(BlendingMode.FREEZE); + public static final BlendComposite Heat = new BlendComposite(BlendingMode.HEAT); + public static final BlendComposite Add = new BlendComposite(BlendingMode.ADD); + public static final BlendComposite Subtract = new BlendComposite(BlendingMode.SUBTRACT); + public static final BlendComposite Stamp = new BlendComposite(BlendingMode.STAMP); + public static final BlendComposite Red = new BlendComposite(BlendingMode.RED); + public static final BlendComposite Green = new BlendComposite(BlendingMode.GREEN); + public static final BlendComposite Blue = new BlendComposite(BlendingMode.BLUE); + public static final BlendComposite Hue = new BlendComposite(BlendingMode.HUE); + public static final BlendComposite Saturation = new BlendComposite(BlendingMode.SATURATION); + public static final BlendComposite Color = new BlendComposite(BlendingMode.COLOR); + public static final BlendComposite Luminosity = new BlendComposite(BlendingMode.LUMINOSITY); + + private float alpha; + private BlendingMode mode; + + private BlendComposite(BlendingMode mode) { + this(mode, 1.0f); + } + + private BlendComposite(BlendingMode mode, float alpha) { + this.mode = mode; + setAlpha(alpha); + } + + public static BlendComposite getInstance(BlendingMode mode) { + return new BlendComposite(mode); + } + + public static BlendComposite getInstance(BlendingMode mode, float alpha) { + return new BlendComposite(mode, alpha); + } + + public BlendComposite derive(BlendingMode mode) { + return this.mode == mode ? this : new BlendComposite(mode, getAlpha()); + } + + public BlendComposite derive(float alpha) { + return this.alpha == alpha ? this : new BlendComposite(getMode(), alpha); + } + + public float getAlpha() { + return alpha; + } + + public BlendingMode getMode() { + return mode; + } + + private void setAlpha(float alpha) { + if (alpha < 0.0f || alpha > 1.0f) { + throw new IllegalArgumentException( + "alpha must be comprised between 0.0f and 1.0f"); + } + + this.alpha = alpha; + } + + @Override + public int hashCode() { + return Float.floatToIntBits(alpha) * 31 + mode.ordinal(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof BlendComposite)) { + return false; + } + + BlendComposite bc = (BlendComposite) obj; + + if (mode != bc.mode) { + return false; + } + + return alpha == bc.alpha; + } + + public CompositeContext createContext(ColorModel srcColorModel, + ColorModel dstColorModel, + RenderingHints hints) { + return new BlendingContext(this); + } + + private static final class BlendingContext implements CompositeContext { + private final Blender blender; + private final BlendComposite composite; + + private BlendingContext(BlendComposite composite) { + this.composite = composite; + this.blender = Blender.getBlenderFor(composite); + } + + public void dispose() { + } + + public void compose(Raster src, Raster dstIn, WritableRaster dstOut) { + if (src.getSampleModel().getDataType() != DataBuffer.TYPE_INT || + dstIn.getSampleModel().getDataType() != DataBuffer.TYPE_INT || + dstOut.getSampleModel().getDataType() != DataBuffer.TYPE_INT) { + throw new IllegalStateException( + "Source and destination must store pixels as INT."); + } + + int width = Math.min(src.getWidth(), dstIn.getWidth()); + int height = Math.min(src.getHeight(), dstIn.getHeight()); + + float alpha = composite.getAlpha(); + + int[] srcPixel = new int[4]; + int[] dstPixel = new int[4]; + int[] result = new int[4]; + int[] srcPixels = new int[width]; + int[] dstPixels = new int[width]; + + for (int y = 0; y < height; y++) { + dstIn.getDataElements(0, y, width, 1, dstPixels); + if (alpha != 0) { + src.getDataElements(0, y, width, 1, srcPixels); + for (int x = 0; x < width; x++) { + // pixels are stored as INT_ARGB + // our arrays are [R, G, B, A] + int pixel = srcPixels[x]; + srcPixel[0] = (pixel >> 16) & 0xFF; + srcPixel[1] = (pixel >> 8) & 0xFF; + srcPixel[2] = (pixel ) & 0xFF; + srcPixel[3] = (pixel >> 24) & 0xFF; + + pixel = dstPixels[x]; + dstPixel[0] = (pixel >> 16) & 0xFF; + dstPixel[1] = (pixel >> 8) & 0xFF; + dstPixel[2] = (pixel ) & 0xFF; + dstPixel[3] = (pixel >> 24) & 0xFF; + + result = blender.blend(srcPixel, dstPixel, result); + + // mixes the result with the opacity + if (alpha == 1) { + dstPixels[x] = (result[3] & 0xFF) << 24 | + (result[0] & 0xFF) << 16 | + (result[1] & 0xFF) << 8 | + result[2] & 0xFF; + } else { + dstPixels[x] = + ((int) (dstPixel[3] + (result[3] - dstPixel[3]) * alpha) & 0xFF) << 24 | + ((int) (dstPixel[0] + (result[0] - dstPixel[0]) * alpha) & 0xFF) << 16 | + ((int) (dstPixel[1] + (result[1] - dstPixel[1]) * alpha) & 0xFF) << 8 | + (int) (dstPixel[2] + (result[2] - dstPixel[2]) * alpha) & 0xFF; + } + + } + } + dstOut.setDataElements(0, y, width, 1, dstPixels); + } + } + } + + private static abstract class Blender { + public abstract int[] blend(int[] src, int[] dst, int[] result); + + private static void RGBtoHSL(int r, int g, int b, float[] hsl) { + float var_R = (r / 255f); + float var_G = (g / 255f); + float var_B = (b / 255f); + + float var_Min; + float var_Max; + float del_Max; + + if (var_R > var_G) { + var_Min = var_G; + var_Max = var_R; + } else { + var_Min = var_R; + var_Max = var_G; + } + if (var_B > var_Max) { + var_Max = var_B; + } + if (var_B < var_Min) { + var_Min = var_B; + } + + del_Max = var_Max - var_Min; + + float H, S, L; + L = (var_Max + var_Min) / 2f; + + if (del_Max - 0.01f <= 0.0f) { + H = 0; + S = 0; + } else { + if (L < 0.5f) { + S = del_Max / (var_Max + var_Min); + } else { + S = del_Max / (2 - var_Max - var_Min); + } + + float del_R = (((var_Max - var_R) / 6f) + (del_Max / 2f)) / del_Max; + float del_G = (((var_Max - var_G) / 6f) + (del_Max / 2f)) / del_Max; + float del_B = (((var_Max - var_B) / 6f) + (del_Max / 2f)) / del_Max; + + if (var_R == var_Max) { + H = del_B - del_G; + } else if (var_G == var_Max) { + H = (1 / 3f) + del_R - del_B; + } else { + H = (2 / 3f) + del_G - del_R; + } + if (H < 0) { + H += 1; + } + if (H > 1) { + H -= 1; + } + } + + hsl[0] = H; + hsl[1] = S; + hsl[2] = L; + } + + private static void HSLtoRGB(float h, float s, float l, int[] rgb) { + int R, G, B; + + if (s - 0.01f <= 0.0f) { + R = (int) (l * 255.0f); + G = (int) (l * 255.0f); + B = (int) (l * 255.0f); + } else { + float var_1, var_2; + if (l < 0.5f) { + var_2 = l * (1 + s); + } else { + var_2 = (l + s) - (s * l); + } + var_1 = 2 * l - var_2; + + R = (int) (255.0f * hue2RGB(var_1, var_2, h + (1.0f / 3.0f))); + G = (int) (255.0f * hue2RGB(var_1, var_2, h)); + B = (int) (255.0f * hue2RGB(var_1, var_2, h - (1.0f / 3.0f))); + } + + rgb[0] = R; + rgb[1] = G; + rgb[2] = B; + } + + private static float hue2RGB(float v1, float v2, float vH) { + if (vH < 0.0f) { + vH += 1.0f; + } + if (vH > 1.0f) { + vH -= 1.0f; + } + if ((6.0f * vH) < 1.0f) { + return (v1 + (v2 - v1) * 6.0f * vH); + } + if ((2.0f * vH) < 1.0f) { + return (v2); + } + if ((3.0f * vH) < 2.0f) { + return (v1 + (v2 - v1) * ((2.0f / 3.0f) - vH) * 6.0f); + } + return (v1); + } + + public static Blender getBlenderFor(BlendComposite composite) { + switch (composite.getMode()) { + case NORMAL: + return new Blender() { + @Override + public int[] blend(int[] src, int[] dst, int[] result) { + System.arraycopy(src, 0, result, 0, 4); + return result; + } + }; + case ADD: + return new Blender() { + @Override + public int[] blend(int[] src, int[] dst, int[] result) { + for (int i = 0; i < 4; i++) { + result[i] = Math.min(255, src[i] + dst[i]); + } + return result; + } + }; + case AVERAGE: + return new Blender() { + @Override + public int[] blend(int[] src, int[] dst, int[] result) { + for (int i = 0; i < 3; i++) { + result[i] = (src[i] + dst[i]) >> 1; + } + result[3] = Math.min(255, src[3] + dst[3]); + return result; + } + }; + case BLUE: + return new Blender() { + @Override + public int[] blend(int[] src, int[] dst, int[] result) { + System.arraycopy(dst, 0, result, 0, 3); + result[3] = Math.min(255, src[3] + dst[3]); + return result; + } + }; + case COLOR: + return new Blender() { + @Override + public int[] blend(int[] src, int[] dst, int[] result) { + float[] srcHSL = new float[3]; + RGBtoHSL(src[0], src[1], src[2], srcHSL); + float[] dstHSL = new float[3]; + RGBtoHSL(dst[0], dst[1], dst[2], dstHSL); + + HSLtoRGB(srcHSL[0], srcHSL[1], dstHSL[2], result); + result[3] = Math.min(255, src[3] + dst[3]); + + return result; + } + }; + case COLOR_BURN: + return new Blender() { + @Override + public int[] blend(int[] src, int[] dst, int[] result) { + for (int i = 0; i < 3; i++) { + result[i] = src[i] == 0 ? 0 : + Math.max(0, 255 - (((255 - dst[i]) << 8) / src[i])); + } + result[3] = Math.min(255, src[3] + dst[3]); + return result; + } + }; + case COLOR_DODGE: + return new Blender() { + @Override + public int[] blend(int[] src, int[] dst, int[] result) { + for (int i = 0; i < 3; i++) { + result[i] = src[i] == 255 ? 255 : + Math.min((dst[i] << 8) / (255 - src[i]), 255); + } + result[3] = Math.min(255, src[3] + dst[3]); + return result; + } + }; + case DARKEN: + return new Blender() { + @Override + public int[] blend(int[] src, int[] dst, int[] result) { + for (int i = 0; i < 3; i++) { + result[i] = Math.min(src[i], dst[i]); + } + result[3] = Math.min(255, src[3] + dst[3]); + return result; + } + }; + case DIFFERENCE: + return new Blender() { + @Override + public int[] blend(int[] src, int[] dst, int[] result) { + for (int i = 0; i < 3; i++) { + result[i] = dst[i] + src[i] - (dst[i] * src[i] >> 7); + } + result[3] = Math.min(255, src[3] + dst[3]); + return result; + } + }; + case EXCLUSION: + return new Blender() { + @Override + public int[] blend(int[] src, int[] dst, int[] result) { + for (int i = 0; i < 3; i++) { + result[i] = dst[i] + src[i] - (dst[i] * src[i] >> 7); + } + result[3] = Math.min(255, src[3] + dst[3]); + return result; + } + }; + case FREEZE: + return new Blender() { + @Override + public int[] blend(int[] src, int[] dst, int[] result) { + for (int i = 0; i < 3; i++) { + result[i] = src[i] == 0 ? 0 : + Math.max(0, 255 - (255 - dst[i]) * (255 - dst[i]) / src[i]); + } + result[3] = Math.min(255, src[3] + dst[3]); + return result; + } + }; + case GLOW: + return new Blender() { + @Override + public int[] blend(int[] src, int[] dst, int[] result) { + for (int i = 0; i < 3; i++) { + result[i] = dst[i] == 255 ? 255 : + Math.min(255, src[i] * src[i] / (255 - dst[i])); + } + result[3] = Math.min(255, src[3] + dst[3]); + return result; + } + }; + case GREEN: + return new Blender() { + @Override + public int[] blend(int[] src, int[] dst, int[] result) { + return new int[] { + dst[0], + dst[1], + src[2], + Math.min(255, src[3] + dst[3]) + }; + } + }; + case HARD_LIGHT: + return new Blender() { + @Override + public int[] blend(int[] src, int[] dst, int[] result) { + return new int[] { + src[0] < 128 ? dst[0] * src[0] >> 7 : + 255 - ((255 - src[0]) * (255 - dst[0]) >> 7), + src[1] < 128 ? dst[1] * src[1] >> 7 : + 255 - ((255 - src[1]) * (255 - dst[1]) >> 7), + src[2] < 128 ? dst[2] * src[2] >> 7 : + 255 - ((255 - src[2]) * (255 - dst[2]) >> 7), + Math.min(255, src[3] + dst[3]) + }; + } + }; + case HEAT: + return new Blender() { + @Override + public int[] blend(int[] src, int[] dst, int[] result) { + return new int[] { + dst[0] == 0 ? 0 : Math.max(0, 255 - (255 - src[0]) * (255 - src[0]) / dst[0]), + dst[1] == 0 ? 0 : Math.max(0, 255 - (255 - src[1]) * (255 - src[1]) / dst[1]), + dst[2] == 0 ? 0 : Math.max(0, 255 - (255 - src[2]) * (255 - src[2]) / dst[2]), + Math.min(255, src[3] + dst[3]) + }; + } + }; + case HUE: + return new Blender() { + @Override + public int[] blend(int[] src, int[] dst, int[] result) { + float[] srcHSL = new float[3]; + RGBtoHSL(src[0], src[1], src[2], srcHSL); + float[] dstHSL = new float[3]; + RGBtoHSL(dst[0], dst[1], dst[2], dstHSL); + + HSLtoRGB(srcHSL[0], dstHSL[1], dstHSL[2], result); + result[3] = Math.min(255, src[3] + dst[3]); + + return result; + } + }; + case INVERSE_COLOR_BURN: + return new Blender() { + @Override + public int[] blend(int[] src, int[] dst, int[] result) { + return new int[] { + dst[0] == 0 ? 0 : + Math.max(0, 255 - (((255 - src[0]) << 8) / dst[0])), + dst[1] == 0 ? 0 : + Math.max(0, 255 - (((255 - src[1]) << 8) / dst[1])), + dst[2] == 0 ? 0 : + Math.max(0, 255 - (((255 - src[2]) << 8) / dst[2])), + Math.min(255, src[3] + dst[3]) + }; + } + }; + case INVERSE_COLOR_DODGE: + return new Blender() { + @Override + public int[] blend(int[] src, int[] dst, int[] result) { + return new int[] { + dst[0] == 255 ? 255 : + Math.min((src[0] << 8) / (255 - dst[0]), 255), + dst[1] == 255 ? 255 : + Math.min((src[1] << 8) / (255 - dst[1]), 255), + dst[2] == 255 ? 255 : + Math.min((src[2] << 8) / (255 - dst[2]), 255), + Math.min(255, src[3] + dst[3]) + }; + } + }; + case LIGHTEN: + return new Blender() { + @Override + public int[] blend(int[] src, int[] dst, int[] result) { + for (int i = 0; i < 3; i++) { + result[i] = Math.max(src[i], dst[i]); + } + result[3] = Math.min(255, src[3] + dst[3]); + return result; + } + }; + case LUMINOSITY: + return new Blender() { + @Override + public int[] blend(int[] src, int[] dst, int[] result) { + float[] srcHSL = new float[3]; + RGBtoHSL(src[0], src[1], src[2], srcHSL); + float[] dstHSL = new float[3]; + RGBtoHSL(dst[0], dst[1], dst[2], dstHSL); + + HSLtoRGB(dstHSL[0], dstHSL[1], srcHSL[2], result); + result[3] = Math.min(255, src[3] + dst[3]); + + return result; + } + }; + case MULTIPLY: + return new Blender() { + @Override + public int[] blend(int[] src, int[] dst, int[] result) { + for (int i = 0; i < 3; i++) { + result[i] = (src[i] * dst[i]) >> 8; + } + result[3] = Math.min(255, src[3] + dst[3]); + return result; + } + }; + case NEGATION: + return new Blender() { + @Override + public int[] blend(int[] src, int[] dst, int[] result) { + return new int[] { + 255 - Math.abs(255 - dst[0] - src[0]), + 255 - Math.abs(255 - dst[1] - src[1]), + 255 - Math.abs(255 - dst[2] - src[2]), + Math.min(255, src[3] + dst[3]) + }; + } + }; + case OVERLAY: + return new Blender() { + @Override + public int[] blend(int[] src, int[] dst, int[] result) { + for (int i = 0; i < 3; i++) { + result[i] = dst[i] < 128 ? dst[i] * src[i] >> 7 : + 255 - ((255 - dst[i]) * (255 - src[i]) >> 7); + } + result[3] = Math.min(255, src[3] + dst[3]); + return result; + } + }; + case RED: + return new Blender() { + @Override + public int[] blend(int[] src, int[] dst, int[] result) { + return new int[] { + src[0], + dst[1], + dst[2], + Math.min(255, src[3] + dst[3]) + }; + } + }; + case REFLECT: + return new Blender() { + @Override + public int[] blend(int[] src, int[] dst, int[] result) { + return new int[] { + src[0] == 255 ? 255 : Math.min(255, dst[0] * dst[0] / (255 - src[0])), + src[1] == 255 ? 255 : Math.min(255, dst[1] * dst[1] / (255 - src[1])), + src[2] == 255 ? 255 : Math.min(255, dst[2] * dst[2] / (255 - src[2])), + Math.min(255, src[3] + dst[3]) + }; + } + }; + case SATURATION: + return new Blender() { + @Override + public int[] blend(int[] src, int[] dst, int[] result) { + float[] srcHSL = new float[3]; + RGBtoHSL(src[0], src[1], src[2], srcHSL); + float[] dstHSL = new float[3]; + RGBtoHSL(dst[0], dst[1], dst[2], dstHSL); + + HSLtoRGB(dstHSL[0], srcHSL[1], dstHSL[2], result); + result[3] = Math.min(255, src[3] + dst[3]); + + return result; + } + }; + case SCREEN: + return new Blender() { + @Override + public int[] blend(int[] src, int[] dst, int[] result) { + return new int[] { + 255 - ((255 - src[0]) * (255 - dst[0]) >> 8), + 255 - ((255 - src[1]) * (255 - dst[1]) >> 8), + 255 - ((255 - src[2]) * (255 - dst[2]) >> 8), + Math.min(255, src[3] + dst[3]) + }; + } + }; + case SOFT_BURN: + return new Blender() { + @Override + public int[] blend(int[] src, int[] dst, int[] result) { + return new int[] { + dst[0] + src[0] < 256 ? + (dst[0] == 255 ? 255 : + Math.min(255, (src[0] << 7) / (255 - dst[0]))) : + Math.max(0, 255 - (((255 - dst[0]) << 7) / src[0])), + dst[1] + src[1] < 256 ? + (dst[1] == 255 ? 255 : + Math.min(255, (src[1] << 7) / (255 - dst[1]))) : + Math.max(0, 255 - (((255 - dst[1]) << 7) / src[1])), + dst[2] + src[2] < 256 ? + (dst[2] == 255 ? 255 : + Math.min(255, (src[2] << 7) / (255 - dst[2]))) : + Math.max(0, 255 - (((255 - dst[2]) << 7) / src[2])), + Math.min(255, src[3] + dst[3]) + }; + } + }; + case SOFT_DODGE: + return new Blender() { + @Override + public int[] blend(int[] src, int[] dst, int[] result) { + return new int[] { + dst[0] + src[0] < 256 ? + (src[0] == 255 ? 255 : + Math.min(255, (dst[0] << 7) / (255 - src[0]))) : + Math.max(0, 255 - (((255 - src[0]) << 7) / dst[0])), + dst[1] + src[1] < 256 ? + (src[1] == 255 ? 255 : + Math.min(255, (dst[1] << 7) / (255 - src[1]))) : + Math.max(0, 255 - (((255 - src[1]) << 7) / dst[1])), + dst[2] + src[2] < 256 ? + (src[2] == 255 ? 255 : + Math.min(255, (dst[2] << 7) / (255 - src[2]))) : + Math.max(0, 255 - (((255 - src[2]) << 7) / dst[2])), + Math.min(255, src[3] + dst[3]) + }; + } + }; + case SOFT_LIGHT: + break; + case STAMP: + return new Blender() { + @Override + public int[] blend(int[] src, int[] dst, int[] result) { + return new int[] { + Math.max(0, Math.min(255, dst[0] + 2 * src[0] - 256)), + Math.max(0, Math.min(255, dst[1] + 2 * src[1] - 256)), + Math.max(0, Math.min(255, dst[2] + 2 * src[2] - 256)), + Math.min(255, src[3] + dst[3]) + }; + } + }; + case SUBTRACT: + return new Blender() { + @Override + public int[] blend(int[] src, int[] dst, int[] result) { + return new int[] { + Math.max(0, src[0] + dst[0] - 256), + Math.max(0, src[1] + dst[1] - 256), + Math.max(0, src[2] + dst[2] - 256), + Math.min(255, src[3] + dst[3]) + }; + } + }; + } + throw new IllegalArgumentException("Blender not implement for " + + composite.getMode().name()); + } + } +} |