diff options
145 files changed, 5323 insertions, 2659 deletions
diff --git a/api/current.txt b/api/current.txt index 8642eee331de..9586d8ee7a01 100644 --- a/api/current.txt +++ b/api/current.txt @@ -852,7 +852,7 @@ package android { field public static final int mirrorForRtl = 16843726; // 0x10103ce field public static final int mode = 16843134; // 0x101017e field public static final int moreIcon = 16843061; // 0x1010135 - field public static final int multiArch = 16843918; // 0x101048e + field public static final int multiArch = 16843919; // 0x101048f field public static final int multiprocess = 16842771; // 0x1010013 field public static final int name = 16842755; // 0x1010003 field public static final int navigationBarColor = 16843860; // 0x1010454 @@ -918,6 +918,7 @@ package android { field public static final int popupAnimationStyle = 16843465; // 0x10102c9 field public static final int popupBackground = 16843126; // 0x1010176 field public static final int popupCharacters = 16843332; // 0x1010244 + field public static final int popupElevation = 16843918; // 0x101048e field public static final int popupKeyboard = 16843331; // 0x1010243 field public static final int popupLayout = 16843323; // 0x101023b field public static final int popupMenuStyle = 16843520; // 0x1010300 @@ -3661,15 +3662,27 @@ package android.app { method public void update(android.app.ActivityOptions); } + public class AlarmClockInfo implements android.os.Parcelable { + ctor public AlarmClockInfo(long, android.app.PendingIntent); + method public int describeContents(); + method public android.app.PendingIntent getShowIntent(); + method public long getTriggerTime(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + public class AlarmManager { method public void cancel(android.app.PendingIntent); + method public android.app.AlarmClockInfo getNextAlarmClock(); method public void set(int, long, android.app.PendingIntent); + method public void setAlarmClock(android.app.AlarmClockInfo, android.app.PendingIntent); method public void setExact(int, long, android.app.PendingIntent); method public void setInexactRepeating(int, long, long, android.app.PendingIntent); method public void setRepeating(int, long, long, android.app.PendingIntent); method public void setTime(long); method public void setTimeZone(java.lang.String); method public void setWindow(int, long, long, android.app.PendingIntent); + field public static final java.lang.String ACTION_NEXT_ALARM_CLOCK_CHANGED = "android.app.action.NEXT_ALARM_CLOCK_CHANGED"; field public static final int ELAPSED_REALTIME = 3; // 0x3 field public static final int ELAPSED_REALTIME_WAKEUP = 2; // 0x2 field public static final long INTERVAL_DAY = 86400000L; // 0x5265c00L @@ -8346,12 +8359,10 @@ package android.content.pm { public class LauncherApps { method public void addOnAppsChangedCallback(android.content.pm.LauncherApps.OnAppsChangedCallback); - method public void addOnAppsChangedListener(android.content.pm.LauncherApps.OnAppsChangedListener); method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(java.lang.String, android.os.UserHandle); method public boolean isActivityEnabledForProfile(android.content.ComponentName, android.os.UserHandle); method public boolean isPackageEnabledForProfile(java.lang.String, android.os.UserHandle); method public void removeOnAppsChangedCallback(android.content.pm.LauncherApps.OnAppsChangedCallback); - method public void removeOnAppsChangedListener(android.content.pm.LauncherApps.OnAppsChangedListener); method public android.content.pm.LauncherActivityInfo resolveActivity(android.content.Intent, android.os.UserHandle); method public void startActivityForProfile(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle); } @@ -8365,14 +8376,6 @@ package android.content.pm { method public abstract void onPackagesUnavailable(java.lang.String[], android.os.UserHandle, boolean); } - public static abstract interface LauncherApps.OnAppsChangedListener { - method public abstract void onPackageAdded(android.os.UserHandle, java.lang.String); - method public abstract void onPackageChanged(android.os.UserHandle, java.lang.String); - method public abstract void onPackageRemoved(android.os.UserHandle, java.lang.String); - method public abstract void onPackagesAvailable(android.os.UserHandle, java.lang.String[], boolean); - method public abstract void onPackagesUnavailable(android.os.UserHandle, java.lang.String[], boolean); - } - public class PackageInfo implements android.os.Parcelable { ctor public PackageInfo(); method public int describeContents(); @@ -12600,9 +12603,10 @@ package android.hardware.camera2 { field public static final int NOISE_REDUCTION_MODE_FAST = 1; // 0x1 field public static final int NOISE_REDUCTION_MODE_HIGH_QUALITY = 2; // 0x2 field public static final int NOISE_REDUCTION_MODE_OFF = 0; // 0x0 - field public static final int REQUEST_AVAILABLE_CAPABILITIES_DNG = 5; // 0x5 - field public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING = 3; // 0x3 - field public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR = 2; // 0x2 + field public static final int REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE = 0; // 0x0 + field public static final int REQUEST_AVAILABLE_CAPABILITIES_DNG = 3; // 0x3 + field public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING = 2; // 0x2 + field public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR = 1; // 0x1 field public static final int SCALER_CROPPING_TYPE_CENTER_ONLY = 0; // 0x0 field public static final int SCALER_CROPPING_TYPE_FREEFORM = 1; // 0x1 field public static final int SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_BGGR = 3; // 0x3 @@ -12829,6 +12833,12 @@ package android.hardware.camera2 { package android.hardware.camera2.params { + public final class BlackLevelPattern { + method public void copyTo(int[], int); + method public int getOffsetForIndex(int, int); + field public static final int COUNT = 4; // 0x4 + } + public final class ColorSpaceTransform { ctor public ColorSpaceTransform(android.util.Rational[]); ctor public ColorSpaceTransform(int[]); @@ -12985,12 +12995,14 @@ package android.hardware.location { field public static final int GEOFENCE_ENTERED = 1; // 0x1 field public static final int GEOFENCE_ERROR_ID_EXISTS = 2; // 0x2 field public static final int GEOFENCE_ERROR_ID_UNKNOWN = 3; // 0x3 + field public static final int GEOFENCE_ERROR_INSUFFICIENT_MEMORY = 6; // 0x6 field public static final int GEOFENCE_ERROR_INVALID_TRANSITION = 4; // 0x4 field public static final int GEOFENCE_ERROR_TOO_MANY_GEOFENCES = 1; // 0x1 field public static final int GEOFENCE_EXITED = 2; // 0x2 field public static final int GEOFENCE_FAILURE = 5; // 0x5 field public static final int GEOFENCE_SUCCESS = 0; // 0x0 field public static final int GEOFENCE_UNCERTAIN = 4; // 0x4 + field public static final int MONITORING_TYPE_FUSED_HARDWARE = 1; // 0x1 field public static final int MONITORING_TYPE_GPS_HARDWARE = 0; // 0x0 field public static final int MONITOR_CURRENTLY_AVAILABLE = 0; // 0x0 field public static final int MONITOR_CURRENTLY_UNAVAILABLE = 1; // 0x1 @@ -16287,10 +16299,12 @@ package android.net { public class ConnectivityManager { method public android.net.NetworkInfo getActiveNetworkInfo(); method public android.net.NetworkInfo[] getAllNetworkInfo(); + method public android.net.Network[] getAllNetworks(); method public deprecated boolean getBackgroundDataSetting(); method public android.net.LinkProperties getLinkProperties(android.net.Network); method public android.net.NetworkCapabilities getNetworkCapabilities(android.net.Network); method public android.net.NetworkInfo getNetworkInfo(int); + method public android.net.NetworkInfo getNetworkInfo(android.net.Network); method public deprecated int getNetworkPreference(); method public static android.net.Network getProcessDefaultNetwork(); method public boolean isActiveNetworkMetered(); @@ -16494,6 +16508,7 @@ package android.net { field public static final int NET_CAPABILITY_MMS = 0; // 0x0 field public static final int NET_CAPABILITY_NOT_METERED = 11; // 0xb field public static final int NET_CAPABILITY_NOT_RESTRICTED = 13; // 0xd + field public static final int NET_CAPABILITY_NOT_VPN = 15; // 0xf field public static final int NET_CAPABILITY_RCS = 8; // 0x8 field public static final int NET_CAPABILITY_SUPL = 1; // 0x1 field public static final int NET_CAPABILITY_TRUSTED = 14; // 0xe @@ -16502,6 +16517,7 @@ package android.net { field public static final int TRANSPORT_BLUETOOTH = 2; // 0x2 field public static final int TRANSPORT_CELLULAR = 0; // 0x0 field public static final int TRANSPORT_ETHERNET = 3; // 0x3 + field public static final int TRANSPORT_VPN = 4; // 0x4 field public static final int TRANSPORT_WIFI = 1; // 0x1 } @@ -16565,6 +16581,7 @@ package android.net { method public android.net.NetworkRequest build(); method public android.net.NetworkRequest.Builder removeCapability(int); method public android.net.NetworkRequest.Builder removeTransportType(int); + method public android.net.NetworkRequest.Builder setNetworkSpecifier(java.lang.String); } public abstract interface PSKKeyManager { @@ -16811,12 +16828,14 @@ package android.net { public class VpnService extends android.app.Service { ctor public VpnService(); + method public boolean addAddress(java.net.InetAddress, int); method public android.os.IBinder onBind(android.content.Intent); method public void onRevoke(); method public static android.content.Intent prepare(android.content.Context); method public boolean protect(int); method public boolean protect(java.net.Socket); method public boolean protect(java.net.DatagramSocket); + method public boolean removeAddress(java.net.InetAddress, int); field public static final java.lang.String SERVICE_INTERFACE = "android.net.VpnService"; } @@ -16824,11 +16843,15 @@ package android.net { ctor public VpnService.Builder(); method public android.net.VpnService.Builder addAddress(java.net.InetAddress, int); method public android.net.VpnService.Builder addAddress(java.lang.String, int); + method public android.net.VpnService.Builder addAllowedApplication(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException; + method public android.net.VpnService.Builder addDisallowedApplication(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException; method public android.net.VpnService.Builder addDnsServer(java.net.InetAddress); method public android.net.VpnService.Builder addDnsServer(java.lang.String); method public android.net.VpnService.Builder addRoute(java.net.InetAddress, int); method public android.net.VpnService.Builder addRoute(java.lang.String, int); method public android.net.VpnService.Builder addSearchDomain(java.lang.String); + method public android.net.VpnService.Builder allowBypass(); + method public android.net.VpnService.Builder allowFamily(int); method public android.os.ParcelFileDescriptor establish(); method public android.net.VpnService.Builder setConfigureIntent(android.app.PendingIntent); method public android.net.VpnService.Builder setMtu(int); @@ -22951,9 +22974,14 @@ package android.provider { field public static final android.net.Uri CONTENT_URI; field public static final android.net.Uri CONTENT_URI_WITH_VOICEMAIL; field public static final java.lang.String COUNTRY_ISO = "countryiso"; + field public static final java.lang.String DATA_USAGE = "data_usage"; field public static final java.lang.String DATE = "date"; field public static final java.lang.String DEFAULT_SORT_ORDER = "date DESC"; field public static final java.lang.String DURATION = "duration"; + field public static final java.lang.String EXTRA_CALL_TYPE_FILTER = "extra_call_type_filter"; + field public static final java.lang.String FEATURES = "features"; + field public static final int FEATURES_NONE = 0; // 0x0 + field public static final int FEATURES_VIDEO = 1; // 0x1 field public static final java.lang.String GEOCODED_LOCATION = "geocoded_location"; field public static final int INCOMING_TYPE = 1; // 0x1 field public static final java.lang.String IS_READ = "is_read"; @@ -23575,6 +23603,7 @@ package android.provider { public static class ContactsContract.Contacts implements android.provider.BaseColumns android.provider.ContactsContract.ContactNameColumns android.provider.ContactsContract.ContactOptionsColumns android.provider.ContactsContract.ContactStatusColumns android.provider.ContactsContract.ContactsColumns { method public static android.net.Uri getLookupUri(android.content.ContentResolver, android.net.Uri); method public static android.net.Uri getLookupUri(long, java.lang.String); + method public static boolean isCorpContactId(long); method public static android.net.Uri lookupContact(android.content.ContentResolver, android.net.Uri); method public static deprecated void markAsContacted(android.content.ContentResolver, long); method public static java.io.InputStream openContactPhotoInputStream(android.content.ContentResolver, android.net.Uri, boolean); @@ -24745,7 +24774,7 @@ package android.provider { field public static final java.lang.String MODE_RINGER_STREAMS_AFFECTED = "mode_ringer_streams_affected"; field public static final java.lang.String MUTE_STREAMS_AFFECTED = "mute_streams_affected"; field public static final deprecated java.lang.String NETWORK_PREFERENCE = "network_preference"; - field public static final java.lang.String NEXT_ALARM_FORMATTED = "next_alarm_formatted"; + field public static final deprecated java.lang.String NEXT_ALARM_FORMATTED = "next_alarm_formatted"; field public static final java.lang.String NOTIFICATION_SOUND = "notification_sound"; field public static final deprecated java.lang.String PARENTAL_CONTROL_ENABLED = "parental_control_enabled"; field public static final deprecated java.lang.String PARENTAL_CONTROL_LAST_UPDATE = "parental_control_last_update"; @@ -26308,16 +26337,17 @@ package android.service.notification { } public static class NotificationListenerService.Ranking { + ctor public NotificationListenerService.Ranking(); method public java.lang.String getKey(); method public int getRank(); method public boolean isAmbient(); - method public boolean isInterceptedByDoNotDisturb(); + method public boolean meetsInterruptionFilter(); } public static class NotificationListenerService.RankingMap implements android.os.Parcelable { method public int describeContents(); method public java.lang.String[] getOrderedKeys(); - method public android.service.notification.NotificationListenerService.Ranking getRanking(java.lang.String); + method public boolean getRanking(java.lang.String, android.service.notification.NotificationListenerService.Ranking); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; } @@ -27436,6 +27466,8 @@ package android.telecomm { field public static final int MERGE_CALLS = 4; // 0x4 field public static final int MUTE = 64; // 0x40 field public static final int RESPOND_VIA_TEXT = 32; // 0x20 + field public static final int SUPPORTS_VT_LOCAL = 256; // 0x100 + field public static final int SUPPORTS_VT_REMOTE = 512; // 0x200 field public static final int SUPPORT_HOLD = 2; // 0x2 field public static final int SWAP_CALLS = 8; // 0x8 } @@ -27534,6 +27566,7 @@ package android.telecomm { method public final boolean getAudioModeIsVoip(); method public final android.telecomm.CallAudioState getCallAudioState(); method public final int getCallCapabilities(); + method public final android.telecomm.CallVideoProvider getCallVideoProvider(); method public final java.util.List<android.telecomm.Connection> getChildConnections(); method public final int getFeatures(); method public final android.net.Uri getHandle(); @@ -30323,10 +30356,34 @@ package android.text.style { field public static final java.lang.String ANIMACY_INANIMATE = "android.inanimate"; field public static final java.lang.String ARG_ANIMACY = "android.arg.animacy"; field public static final java.lang.String ARG_CASE = "android.arg.case"; + field public static final java.lang.String ARG_COUNTRY_CODE = "android.arg.country_code"; + field public static final java.lang.String ARG_DAY = "android.arg.day"; + field public static final java.lang.String ARG_DENOMINATOR = "android.arg.denominator"; + field public static final java.lang.String ARG_DIGITS = "android.arg.digits"; + field public static final java.lang.String ARG_DOMAIN = "android.arg.domain"; + field public static final java.lang.String ARG_EXTENSION = "android.arg.extension"; + field public static final java.lang.String ARG_FRACTIONAL_PART = "android.arg.fractional_part"; + field public static final java.lang.String ARG_FRAGMENT_ID = "android.arg.fragment_id"; field public static final java.lang.String ARG_GENDER = "android.arg.gender"; + field public static final java.lang.String ARG_HOURS = "android.arg.hours"; + field public static final java.lang.String ARG_INTEGER_PART = "android.arg.integer_part"; + field public static final java.lang.String ARG_MINUTES = "android.arg.minutes"; + field public static final java.lang.String ARG_MONTH = "android.arg.month"; field public static final java.lang.String ARG_MULTIPLICITY = "android.arg.multiplicity"; field public static final java.lang.String ARG_NUMBER = "android.arg.number"; + field public static final java.lang.String ARG_NUMBER_PART = "android.arg.number_part"; + field public static final java.lang.String ARG_NUMERATOR = "android.arg.numerator"; + field public static final java.lang.String ARG_PASSWORD = "android.arg.password"; + field public static final java.lang.String ARG_PATH = "android.arg.path"; + field public static final java.lang.String ARG_PORT = "android.arg.port"; + field public static final java.lang.String ARG_PROTOCOL = "android.arg.protocol"; + field public static final java.lang.String ARG_QUERY_STRING = "android.arg.query_string"; field public static final java.lang.String ARG_TEXT = "android.arg.text"; + field public static final java.lang.String ARG_UNIT = "android.arg.unit"; + field public static final java.lang.String ARG_USERNAME = "android.arg.username"; + field public static final java.lang.String ARG_VERBATIM = "android.arg.verbatim"; + field public static final java.lang.String ARG_WEEKDAY = "android.arg.weekday"; + field public static final java.lang.String ARG_YEAR = "android.arg.year"; field public static final java.lang.String CASE_ABLATIVE = "android.ablative"; field public static final java.lang.String CASE_ACCUSATIVE = "android.accusative"; field public static final java.lang.String CASE_DATIVE = "android.dative"; @@ -30338,11 +30395,40 @@ package android.text.style { field public static final java.lang.String GENDER_FEMALE = "android.female"; field public static final java.lang.String GENDER_MALE = "android.male"; field public static final java.lang.String GENDER_NEUTRAL = "android.neutral"; + field public static final int MONTH_APRIL = 3; // 0x3 + field public static final int MONTH_AUGUST = 7; // 0x7 + field public static final int MONTH_DECEMBER = 11; // 0xb + field public static final int MONTH_FEBRUARY = 1; // 0x1 + field public static final int MONTH_JANUARY = 0; // 0x0 + field public static final int MONTH_JULY = 6; // 0x6 + field public static final int MONTH_JUNE = 5; // 0x5 + field public static final int MONTH_MARCH = 2; // 0x2 + field public static final int MONTH_MAY = 4; // 0x4 + field public static final int MONTH_NOVEMBER = 10; // 0xa + field public static final int MONTH_OCTOBER = 9; // 0x9 + field public static final int MONTH_SEPTEMBER = 8; // 0x8 field public static final java.lang.String MULTIPLICITY_DUAL = "android.dual"; field public static final java.lang.String MULTIPLICITY_PLURAL = "android.plural"; field public static final java.lang.String MULTIPLICITY_SINGLE = "android.single"; field public static final java.lang.String TYPE_CARDINAL = "android.type.cardinal"; + field public static final java.lang.String TYPE_DATE = "android.type.date"; + field public static final java.lang.String TYPE_DECIMAL = "android.type.decimal"; + field public static final java.lang.String TYPE_DIGITS = "android.type.digits"; + field public static final java.lang.String TYPE_ELECTRONIC = "android.type.electronic"; + field public static final java.lang.String TYPE_FRACTION = "android.type.fraction"; + field public static final java.lang.String TYPE_MEASURE = "android.type.measure"; + field public static final java.lang.String TYPE_ORDINAL = "android.type.ordinal"; + field public static final java.lang.String TYPE_TELEPHONE = "android.type.telephone"; field public static final java.lang.String TYPE_TEXT = "android.type.text"; + field public static final java.lang.String TYPE_TIME = "android.type.time"; + field public static final java.lang.String TYPE_VERBATIM = "android.type.verbatim"; + field public static final int WEEKDAY_FRIDAY = 6; // 0x6 + field public static final int WEEKDAY_MONDAY = 2; // 0x2 + field public static final int WEEKDAY_SATURDAY = 7; // 0x7 + field public static final int WEEKDAY_SUNDAY = 1; // 0x1 + field public static final int WEEKDAY_THURSDAY = 5; // 0x5 + field public static final int WEEKDAY_TUESDAY = 3; // 0x3 + field public static final int WEEKDAY_WEDNESDAY = 4; // 0x4 } public class TypefaceSpan extends android.text.style.MetricAffectingSpan implements android.text.ParcelableSpan { @@ -37218,6 +37304,7 @@ package android.widget { method public int getAnimationStyle(); method public android.graphics.drawable.Drawable getBackground(); method public android.view.View getContentView(); + method public float getElevation(); method public int getHeight(); method public int getInputMethodMode(); method public int getMaxAvailableHeight(android.view.View); @@ -37235,6 +37322,7 @@ package android.widget { method public void setBackgroundDrawable(android.graphics.drawable.Drawable); method public void setClippingEnabled(boolean); method public void setContentView(android.view.View); + method public void setElevation(float); method public void setFocusable(boolean); method public void setHeight(int); method public void setIgnoreCheekPress(); diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java index 1a03fe95afca..15be9b18289b 100644 --- a/core/java/android/app/ActivityTransitionCoordinator.java +++ b/core/java/android/app/ActivityTransitionCoordinator.java @@ -280,14 +280,6 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { public ArrayList<String> getAllSharedElementNames() { return mAllSharedElementNames; } - public static void setViewVisibility(Collection<View> views, int visibility) { - if (views != null) { - for (View view : views) { - view.setVisibility(visibility); - } - } - } - protected Transition setTargets(Transition transition, boolean add) { if (transition == null || (add && (mTransitioningViews == null || mTransitioningViews.isEmpty()))) { @@ -531,6 +523,13 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { return getWindow().getTransitionBackgroundFadeDuration(); } + protected static void setTransitionAlpha(ArrayList<View> views, float alpha) { + int numSharedElements = views.size(); + for (int i = 0; i < numSharedElements; i++) { + views.get(i).setTransitionAlpha(alpha); + } + } + /** * Captures placement information for Views with a shared element name for * Activity Transitions. diff --git a/core/java/android/app/AlarmClockInfo.aidl b/core/java/android/app/AlarmClockInfo.aidl new file mode 100644 index 000000000000..58a3644d8928 --- /dev/null +++ b/core/java/android/app/AlarmClockInfo.aidl @@ -0,0 +1,19 @@ +/** + * 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.app; + +parcelable AlarmClockInfo; diff --git a/core/java/android/app/AlarmClockInfo.java b/core/java/android/app/AlarmClockInfo.java new file mode 100644 index 000000000000..0ccaf02995ae --- /dev/null +++ b/core/java/android/app/AlarmClockInfo.java @@ -0,0 +1,101 @@ +/* + * 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.app; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * An immutable description of an alarm clock. + * + * @see AlarmManager#setAlarmClock + * @see AlarmManager#getNextAlarmClock + */ +public class AlarmClockInfo implements Parcelable { + + private final long mTriggerTime; + private final PendingIntent mShowIntent; + + /** + * Creates a new alarm clock description. + * + * @param triggerTime time at which the underlying alarm is triggered in wall time milliseconds + * since the epoch + * @param showIntent an intent that can be used to show or edit details of + * the alarm clock. + */ + public AlarmClockInfo(long triggerTime, PendingIntent showIntent) { + mTriggerTime = triggerTime; + mShowIntent = showIntent; + } + + /** + * Use the {@link #CREATOR} + * @hide + */ + AlarmClockInfo(Parcel in) { + mTriggerTime = in.readLong(); + mShowIntent = in.readParcelable(PendingIntent.class.getClassLoader()); + } + + /** + * Returns the time at which the alarm is going to trigger. + * + * This value is UTC wall clock time in milliseconds, as returned by + * {@link System#currentTimeMillis()} for example. + */ + public long getTriggerTime() { + return mTriggerTime; + } + + /** + * Returns an intent intent that can be used to show or edit details of the alarm clock in + * the application that scheduled it. + * + * <p class="note">Beware that any application can retrieve and send this intent, potentially + * with additional fields filled in. See + * {@link PendingIntent#send(android.content.Context, int, android.content.Intent) + * PendingIntent.send()} and {@link android.content.Intent#fillIn Intent.fillIn()} + * for details. + */ + public PendingIntent getShowIntent() { + return mShowIntent; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(mTriggerTime); + dest.writeParcelable(mShowIntent, flags); + } + + public static final Creator<AlarmClockInfo> CREATOR = new Creator<AlarmClockInfo>() { + @Override + public AlarmClockInfo createFromParcel(Parcel in) { + return new AlarmClockInfo(in); + } + + @Override + public AlarmClockInfo[] newArray(int size) { + return new AlarmClockInfo[size]; + } + }; +} diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java index 0cf7ad06a291..fa2d64c9c1a5 100644 --- a/core/java/android/app/AlarmManager.java +++ b/core/java/android/app/AlarmManager.java @@ -16,10 +16,12 @@ package android.app; +import android.annotation.SdkConstant; import android.content.Context; import android.content.Intent; import android.os.Build; import android.os.RemoteException; +import android.os.UserHandle; import android.os.WorkSource; /** @@ -94,6 +96,17 @@ public class AlarmManager */ public static final int ELAPSED_REALTIME = 3; + /** + * Broadcast Action: Sent after the value returned by + * {@link #getNextAlarmClock()} has changed. + * + * <p class="note">This is a protected intent that can only be sent by the system. + * It is only sent to registered receivers.</p> + */ + @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_NEXT_ALARM_CLOCK_CHANGED = + "android.app.action.NEXT_ALARM_CLOCK_CHANGED"; + /** @hide */ public static final long WINDOW_EXACT = 0; /** @hide */ @@ -188,7 +201,7 @@ public class AlarmManager * @see #RTC_WAKEUP */ public void set(int type, long triggerAtMillis, PendingIntent operation) { - setImpl(type, triggerAtMillis, legacyExactLength(), 0, operation, null); + setImpl(type, triggerAtMillis, legacyExactLength(), 0, operation, null, null); } /** @@ -249,7 +262,7 @@ public class AlarmManager */ public void setRepeating(int type, long triggerAtMillis, long intervalMillis, PendingIntent operation) { - setImpl(type, triggerAtMillis, legacyExactLength(), intervalMillis, operation, null); + setImpl(type, triggerAtMillis, legacyExactLength(), intervalMillis, operation, null, null); } /** @@ -299,7 +312,7 @@ public class AlarmManager */ public void setWindow(int type, long windowStartMillis, long windowLengthMillis, PendingIntent operation) { - setImpl(type, windowStartMillis, windowLengthMillis, 0, operation, null); + setImpl(type, windowStartMillis, windowLengthMillis, 0, operation, null, null); } /** @@ -337,17 +350,45 @@ public class AlarmManager * @see #RTC_WAKEUP */ public void setExact(int type, long triggerAtMillis, PendingIntent operation) { - setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, operation, null); + setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, operation, null, null); + } + + /** + * Schedule an alarm that represents an alarm clock. + * + * The system may choose to display information about this alarm to the user. + * + * <p> + * This method is like {@link #setExact(int, long, PendingIntent)}, but implies + * {@link #RTC_WAKEUP}. + * + * @param info + * @param operation Action to perform when the alarm goes off; + * typically comes from {@link PendingIntent#getBroadcast + * IntentSender.getBroadcast()}. + * + * @see #set + * @see #setRepeating + * @see #setWindow + * @see #setExact + * @see #cancel + * @see #getNextAlarmClock() + * @see android.content.Context#sendBroadcast + * @see android.content.Context#registerReceiver + * @see android.content.Intent#filterEquals + */ + public void setAlarmClock(AlarmClockInfo info, PendingIntent operation) { + setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0, operation, null, info); } /** @hide */ public void set(int type, long triggerAtMillis, long windowMillis, long intervalMillis, PendingIntent operation, WorkSource workSource) { - setImpl(type, triggerAtMillis, windowMillis, intervalMillis, operation, workSource); + setImpl(type, triggerAtMillis, windowMillis, intervalMillis, operation, workSource, null); } private void setImpl(int type, long triggerAtMillis, long windowMillis, long intervalMillis, - PendingIntent operation, WorkSource workSource) { + PendingIntent operation, WorkSource workSource, AlarmClockInfo alarmClock) { if (triggerAtMillis < 0) { /* NOTYET if (mAlwaysExact) { @@ -361,7 +402,7 @@ public class AlarmManager try { mService.set(type, triggerAtMillis, windowMillis, intervalMillis, operation, - workSource); + workSource, alarmClock); } catch (RemoteException ex) { } } @@ -461,7 +502,7 @@ public class AlarmManager */ public void setInexactRepeating(int type, long triggerAtMillis, long intervalMillis, PendingIntent operation) { - setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, intervalMillis, operation, null); + setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, intervalMillis, operation, null, null); } /** @@ -506,4 +547,36 @@ public class AlarmManager } catch (RemoteException ex) { } } + + /** + * Gets information about the next alarm clock currently scheduled. + * + * The alarm clocks considered are those scheduled by {@link #setAlarmClock} + * from any package of the calling user. + * + * @see #setAlarmClock + * @see AlarmClockInfo + */ + public AlarmClockInfo getNextAlarmClock() { + return getNextAlarmClock(UserHandle.myUserId()); + } + + /** + * Gets information about the next alarm clock currently scheduled. + * + * The alarm clocks considered are those scheduled by {@link #setAlarmClock} + * from any package of the given {@parm userId}. + * + * @see #setAlarmClock + * @see AlarmClockInfo + * + * @hide + */ + public AlarmClockInfo getNextAlarmClock(int userId) { + try { + return mService.getNextAlarmClock(userId); + } catch (RemoteException ex) { + return null; + } + } } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index cacb2dfad83a..bbfb05e12ecc 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -565,9 +565,7 @@ class ContextImpl extends Context { registerService(PHONE_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { - IBinder b = ServiceManager.getService(TELECOMM_SERVICE); - return new PhoneManager(ctx.getOuterContext(), - ITelecommService.Stub.asInterface(b)); + return new PhoneManager(ctx.getOuterContext()); }}); registerService(UI_MODE_SERVICE, new ServiceFetcher() { diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java index 90f36b8bc088..f50c93b3cc0e 100644 --- a/core/java/android/app/EnterTransitionCoordinator.java +++ b/core/java/android/app/EnterTransitionCoordinator.java @@ -104,9 +104,9 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS); send(MSG_SEND_SHARED_ELEMENT_DESTINATION, null); } - setViewVisibility(mSharedElements, View.INVISIBLE); + setTransitionAlpha(mSharedElements, 0); if (getViewsTransition() != null) { - setViewVisibility(mTransitioningViews, View.INVISIBLE); + setTransitionAlpha(mTransitioningViews, 0); } if (mSharedElementsBundle != null) { onTakeSharedElements(); @@ -221,7 +221,7 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { if (!mIsCanceled) { mIsCanceled = true; if (getViewsTransition() == null || mIsViewsTransitionStarted) { - setViewVisibility(mSharedElements, View.VISIBLE); + setTransitionAlpha(mSharedElements, 1); } else { mTransitioningViews.addAll(mSharedElements); } @@ -281,7 +281,7 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { // Now start shared element transition ArrayList<View> sharedElementSnapshots = createSnapshots(sharedElementState, mSharedElementNames); - setViewVisibility(mSharedElements, View.VISIBLE); + setTransitionAlpha(mSharedElements, 1); ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageViewState = setSharedElementState(sharedElementState, sharedElementSnapshots); requestLayoutForSharedElements(); @@ -314,13 +314,6 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { } } - @Override - protected void stripOffscreenViews() { - setViewVisibility(mTransitioningViews, View.VISIBLE); - super.stripOffscreenViews(); - setViewVisibility(mTransitioningViews, View.INVISIBLE); - } - private void onTakeSharedElements() { if (!mIsReadyForTransition || mSharedElementsBundle == null) { return; @@ -362,6 +355,8 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { viewsTransition = configureTransition(getViewsTransition(), true); if (viewsTransition != null) { stripOffscreenViews(); + viewsTransition.forceVisibility(View.INVISIBLE, true); + setTransitionAlpha(mTransitioningViews, 1); } } mIsViewsTransitionStarted = mIsViewsTransitionStarted || startEnterTransition; @@ -405,7 +400,6 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { } private void startEnterTransition(Transition transition) { - setViewVisibility(mTransitioningViews, View.VISIBLE); if (!mIsReturning) { Drawable background = getDecor().getBackground(); if (background != null) { diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java index 0fbd68543ac4..3f3e00c42919 100644 --- a/core/java/android/app/ExitTransitionCoordinator.java +++ b/core/java/android/app/ExitTransitionCoordinator.java @@ -68,6 +68,8 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { private boolean mIsExitStarted; + private boolean mSharedElementsHidden; + public ExitTransitionCoordinator(Activity activity, ArrayList<String> names, ArrayList<String> accepted, ArrayList<View> mapped, boolean isReturning) { super(activity.getWindow(), names, getListener(activity, isReturning), @@ -113,8 +115,8 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { } public void resetViews() { - setViewVisibility(mTransitioningViews, View.VISIBLE); - setViewVisibility(mSharedElements, View.VISIBLE); + setTransitionAlpha(mTransitioningViews, 1); + setTransitionAlpha(mSharedElements, 1); mIsHidden = true; clearState(); } @@ -161,8 +163,9 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { private void hideSharedElements() { if (!mIsHidden) { - setViewVisibility(mSharedElements, View.INVISIBLE); + setTransitionAlpha(mSharedElements, 0); } + mSharedElementsHidden = true; finishIfNecessary(); } @@ -175,7 +178,6 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { beginTransitions(); } }); - setViewVisibility(mTransitioningViews, View.INVISIBLE); } } @@ -219,7 +221,7 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { Transition transition = getExitTransition(); if (transition != null) { TransitionManager.beginDelayedTransition(getDecor(), transition); - setViewVisibility(mTransitioningViews, View.INVISIBLE); + mTransitioningViews.get(0).invalidate(); } } @@ -241,6 +243,8 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { }); mBackgroundAnimator.setDuration(getFadeDuration()); mBackgroundAnimator.start(); + } else { + mIsBackgroundReady = true; } } } @@ -259,7 +263,7 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { transition.removeListener(this); exitTransitionComplete(); if (mIsHidden) { - setViewVisibility(mTransitioningViews, View.VISIBLE); + setTransitionAlpha(mTransitioningViews, 1); } } @@ -268,6 +272,7 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { super.onTransitionCancel(transition); } }); + viewsTransition.forceVisibility(View.INVISIBLE, false); } return viewsTransition; } @@ -286,7 +291,7 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { transition.removeListener(this); sharedElementTransitionComplete(); if (mIsHidden) { - setViewVisibility(mSharedElements, View.VISIBLE); + setTransitionAlpha(mSharedElements, 1); } } }); @@ -302,6 +307,7 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { Transition transition = mergeTransitions(sharedElementTransition, viewsTransition); if (transition != null) { TransitionManager.beginDelayedTransition(getDecor(), transition); + getDecor().invalidate(); } } @@ -353,7 +359,7 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { private void finishIfNecessary() { if (mIsReturning && mExitNotified && mActivity != null && (mSharedElements.isEmpty() || - mSharedElements.get(0).getVisibility() == View.INVISIBLE)) { + mSharedElementsHidden)) { finish(); } if (!mIsReturning && mExitNotified) { diff --git a/core/java/android/app/IAlarmManager.aidl b/core/java/android/app/IAlarmManager.aidl index ef9f26eeb6d4..fb33706dde34 100644 --- a/core/java/android/app/IAlarmManager.aidl +++ b/core/java/android/app/IAlarmManager.aidl @@ -16,6 +16,7 @@ */ package android.app; +import android.app.AlarmClockInfo; import android.app.PendingIntent; import android.os.WorkSource; @@ -27,10 +28,12 @@ import android.os.WorkSource; interface IAlarmManager { /** windowLength == 0 means exact; windowLength < 0 means the let the OS decide */ void set(int type, long triggerAtTime, long windowLength, - long interval, in PendingIntent operation, in WorkSource workSource); + long interval, in PendingIntent operation, in WorkSource workSource, + in AlarmClockInfo alarmClock); boolean setTime(long millis); void setTimeZone(String zone); void remove(in PendingIntent operation); + AlarmClockInfo getNextAlarmClock(int userId); } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 04275d84f65d..535aaa1a936b 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2422,6 +2422,7 @@ public abstract class Context { * @see android.net.wifi.WifiScanner * @hide */ + @SystemApi public static final String WIFI_SCANNING_SERVICE = "wifiscanner"; /** diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 1e93d92f53f9..4939fb653301 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -491,13 +491,55 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public String nativeLibraryDir; /** - * The ABI that this application requires, This is inferred from the ABIs + * The path under the apps data directory we store unpacked libraries. For + * new installs, we create subdirectories under legacyNativeLibraryDir that are + * architecture specific. For legacy installs, the shared libraries are + * placed directly under this path. + * + * For "legacy" installs {@code nativeLibraryDir} will be equal to this path. + * For newer installs, it will be derived based on the codePath and the primary + * cpu abi. + * + * @hide. + */ + public String legacyNativeLibraryDir; + + /** + * The primary ABI that this application requires, This is inferred from the ABIs * of the native JNI libraries the application bundles. Will be {@code null} * if this application does not require any particular ABI. * + * If non-null, the application will always be launched with this ABI. + * + * {@hide} + */ + public String primaryCpuAbi; + + /** + * The secondary ABI for this application. Might be non-null for multi-arch + * installs. The application itself never uses this ABI, but other applications that + * use its code might. + * + * {@hide} + */ + public String secondaryCpuAbi; + + /** + * The derived APK "root" for the given package. Will be non-null for bundled and + * updated system apps. This will be a top level path under which apks and libraries + * are installed, for eg. {@code /system}, {@code /oem} or {@code /vendor}. This is + * used to calculate the location of native code for a given package, for e.g + * {@code /vendor/lib} or {@code /vendor/lib64}. + * + * For app updates or fresh app installs, this will be {@code null} and we will use + * {@code legacyNativeLibraryDir} + * + * NOTE: This can be removed if we have a unified layout for bundled and installed + * apps. + * * {@hide} */ - public String cpuAbi; + public String apkRoot; /** * The kernel user-ID that has been assigned to this application; @@ -641,7 +683,10 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { splitSourceDirs = orig.splitSourceDirs; splitPublicSourceDirs = orig.splitPublicSourceDirs; nativeLibraryDir = orig.nativeLibraryDir; - cpuAbi = orig.cpuAbi; + legacyNativeLibraryDir = orig.legacyNativeLibraryDir; + primaryCpuAbi = orig.primaryCpuAbi; + secondaryCpuAbi = orig.secondaryCpuAbi; + apkRoot = orig.apkRoot; resourceDirs = orig.resourceDirs; seinfo = orig.seinfo; sharedLibraryFiles = orig.sharedLibraryFiles; @@ -685,7 +730,10 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { dest.writeStringArray(splitSourceDirs); dest.writeStringArray(splitPublicSourceDirs); dest.writeString(nativeLibraryDir); - dest.writeString(cpuAbi); + dest.writeString(legacyNativeLibraryDir); + dest.writeString(primaryCpuAbi); + dest.writeString(secondaryCpuAbi); + dest.writeString(apkRoot); dest.writeStringArray(resourceDirs); dest.writeString(seinfo); dest.writeStringArray(sharedLibraryFiles); @@ -728,7 +776,10 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { splitSourceDirs = source.readStringArray(); splitPublicSourceDirs = source.readStringArray(); nativeLibraryDir = source.readString(); - cpuAbi = source.readString(); + legacyNativeLibraryDir = source.readString(); + primaryCpuAbi = source.readString(); + secondaryCpuAbi = source.readString(); + apkRoot = source.readString(); resourceDirs = source.readStringArray(); seinfo = source.readString(); sharedLibraryFiles = source.readStringArray(); diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 6c10bb8fce7a..6e7a41832320 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -55,8 +55,6 @@ public class LauncherApps { private ILauncherApps mService; private PackageManager mPm; - private List<OnAppsChangedListener> mListeners - = new ArrayList<OnAppsChangedListener>(); private List<OnAppsChangedCallback> mCallbacks = new ArrayList<OnAppsChangedCallback>(); @@ -117,62 +115,6 @@ public class LauncherApps { boolean replacing); } - /** - * Callbacks for package changes to this and related managed profiles. - */ - public interface OnAppsChangedListener { - /** - * Indicates that a package was removed from the specified profile. - * - * @param user The UserHandle of the profile that generated the change. - * @param packageName The name of the package that was removed. - */ - void onPackageRemoved(UserHandle user, String packageName); - - /** - * Indicates that a package was added to the specified profile. - * - * @param user The UserHandle of the profile that generated the change. - * @param packageName The name of the package that was added. - */ - void onPackageAdded(UserHandle user, String packageName); - - /** - * Indicates that a package was modified in the specified profile. - * - * @param user The UserHandle of the profile that generated the change. - * @param packageName The name of the package that has changed. - */ - void onPackageChanged(UserHandle user, String packageName); - - /** - * Indicates that one or more packages have become available. For - * example, this can happen when a removable storage card has - * reappeared. - * - * @param user The UserHandle of the profile that generated the change. - * @param packageNames The names of the packages that have become - * available. - * @param replacing Indicates whether these packages are replacing - * existing ones. - */ - void onPackagesAvailable(UserHandle user, String[] packageNames, boolean replacing); - - /** - * Indicates that one or more packages have become unavailable. For - * example, this can happen when a removable storage card has been - * removed. - * - * @param user The UserHandle of the profile that generated the change. - * @param packageNames The names of the packages that have become - * unavailable. - * @param replacing Indicates whether the packages are about to be - * replaced with new versions. - */ - void onPackagesUnavailable(UserHandle user, String[] packageNames, boolean replacing); - - } - /** @hide */ public LauncherApps(Context context, ILauncherApps service) { mContext = context; @@ -321,43 +263,6 @@ public class LauncherApps { /** - * Adds a listener for changes to packages in current and managed profiles. - * - * @param listener The listener to add. - */ - public void addOnAppsChangedListener(OnAppsChangedListener listener) { - synchronized (this) { - if (listener != null && !mListeners.contains(listener)) { - mListeners.add(listener); - if (mListeners.size() == 1 && mCallbacks.size() == 0) { - try { - mService.addOnAppsChangedListener(mAppsChangedListener); - } catch (RemoteException re) { - } - } - } - } - } - - /** - * Removes a listener that was previously added. - * - * @param listener The listener to remove. - * @see #addOnAppsChangedListener(OnAppsChangedListener) - */ - public void removeOnAppsChangedListener(OnAppsChangedListener listener) { - synchronized (this) { - mListeners.remove(listener); - if (mListeners.size() == 0 && mCallbacks.size() == 0) { - try { - mService.removeOnAppsChangedListener(mAppsChangedListener); - } catch (RemoteException re) { - } - } - } - } - - /** * Adds a callback for changes to packages in current and managed profiles. * * @param callback The callback to add. @@ -366,7 +271,7 @@ public class LauncherApps { synchronized (this) { if (callback != null && !mCallbacks.contains(callback)) { mCallbacks.add(callback); - if (mCallbacks.size() == 1 && mListeners.size() == 0) { + if (mCallbacks.size() == 1) { try { mService.addOnAppsChangedListener(mAppsChangedListener); } catch (RemoteException re) { @@ -384,8 +289,8 @@ public class LauncherApps { */ public void removeOnAppsChangedCallback(OnAppsChangedCallback callback) { synchronized (this) { - mListeners.remove(callback); - if (mListeners.size() == 0 && mCallbacks.size() == 0) { + mCallbacks.remove(callback); + if (mCallbacks.size() == 0) { try { mService.removeOnAppsChangedListener(mAppsChangedListener); } catch (RemoteException re) { @@ -402,9 +307,6 @@ public class LauncherApps { Log.d(TAG, "onPackageRemoved " + user.getIdentifier() + "," + packageName); } synchronized (LauncherApps.this) { - for (OnAppsChangedListener listener : mListeners) { - listener.onPackageRemoved(user, packageName); - } for (OnAppsChangedCallback callback : mCallbacks) { callback.onPackageRemoved(packageName, user); } @@ -417,9 +319,6 @@ public class LauncherApps { Log.d(TAG, "onPackageChanged " + user.getIdentifier() + "," + packageName); } synchronized (LauncherApps.this) { - for (OnAppsChangedListener listener : mListeners) { - listener.onPackageChanged(user, packageName); - } for (OnAppsChangedCallback callback : mCallbacks) { callback.onPackageChanged(packageName, user); } @@ -432,9 +331,6 @@ public class LauncherApps { Log.d(TAG, "onPackageAdded " + user.getIdentifier() + "," + packageName); } synchronized (LauncherApps.this) { - for (OnAppsChangedListener listener : mListeners) { - listener.onPackageAdded(user, packageName); - } for (OnAppsChangedCallback callback : mCallbacks) { callback.onPackageAdded(packageName, user); } @@ -448,9 +344,6 @@ public class LauncherApps { Log.d(TAG, "onPackagesAvailable " + user.getIdentifier() + "," + packageNames); } synchronized (LauncherApps.this) { - for (OnAppsChangedListener listener : mListeners) { - listener.onPackagesAvailable(user, packageNames, replacing); - } for (OnAppsChangedCallback callback : mCallbacks) { callback.onPackagesAvailable(packageNames, user, replacing); } @@ -464,9 +357,6 @@ public class LauncherApps { Log.d(TAG, "onPackagesUnavailable " + user.getIdentifier() + "," + packageNames); } synchronized (LauncherApps.this) { - for (OnAppsChangedListener listener : mListeners) { - listener.onPackagesUnavailable(user, packageNames, replacing); - } for (OnAppsChangedCallback callback : mCallbacks) { callback.onPackagesUnavailable(packageNames, user, replacing); } diff --git a/core/java/android/content/pm/PackageInfoLite.java b/core/java/android/content/pm/PackageInfoLite.java index a1566dadbadd..50a048322f83 100644 --- a/core/java/android/content/pm/PackageInfoLite.java +++ b/core/java/android/content/pm/PackageInfoLite.java @@ -37,6 +37,13 @@ public class PackageInfoLite implements Parcelable { public int versionCode; /** + * The android:multiArch flag from the package manifest. If set, + * we will extract all native libraries for the given app, not just those + * from the preferred ABI. + */ + public boolean multiArch; + + /** * Specifies the recommended install location. Can be one of * {@link #PackageHelper.RECOMMEND_INSTALL_INTERNAL} to install on internal storage * {@link #PackageHelper.RECOMMEND_INSTALL_EXTERNAL} to install on external media diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index ab0bf25f3a95..6e0ca505855d 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -249,6 +249,8 @@ public class PackageParser { /** Paths of any split APKs, ordered by parsed splitName */ public final String[] splitCodePaths; + public final boolean multiArch; + private PackageLite(String codePath, ApkLite baseApk, String[] splitNames, String[] splitCodePaths) { this.packageName = baseApk.packageName; @@ -259,6 +261,7 @@ public class PackageParser { this.codePath = codePath; this.baseCodePath = baseApk.codePath; this.splitCodePaths = splitCodePaths; + this.multiArch = baseApk.multiArch; } public List<String> getAllCodePaths() { @@ -282,9 +285,11 @@ public class PackageParser { public final int installLocation; public final VerifierInfo[] verifiers; public final Signature[] signatures; + public final boolean multiArch; public ApkLite(String codePath, String packageName, String splitName, int versionCode, - int installLocation, List<VerifierInfo> verifiers, Signature[] signatures) { + int installLocation, List<VerifierInfo> verifiers, Signature[] signatures, + boolean multiArch) { this.codePath = codePath; this.packageName = packageName; this.splitName = splitName; @@ -292,6 +297,7 @@ public class PackageParser { this.installLocation = installLocation; this.verifiers = verifiers.toArray(new VerifierInfo[verifiers.size()]); this.signatures = signatures; + this.multiArch = multiArch; } } @@ -1114,6 +1120,7 @@ public class PackageParser { int installLocation = PARSE_DEFAULT_INSTALL_LOCATION; int versionCode = 0; int numFound = 0; + boolean multiArch = false; for (int i = 0; i < attrs.getAttributeCount(); i++) { String attr = attrs.getAttributeName(i); if (attr.equals("installLocation")) { @@ -1123,8 +1130,11 @@ public class PackageParser { } else if (attr.equals("versionCode")) { versionCode = attrs.getAttributeIntValue(i, 0); numFound++; + } else if (attr.equals("multiArch")) { + multiArch = attrs.getAttributeBooleanValue(i, false); + numFound++; } - if (numFound >= 2) { + if (numFound >= 3) { break; } } @@ -1149,7 +1159,7 @@ public class PackageParser { } return new ApkLite(codePath, packageSplit.first, packageSplit.second, versionCode, - installLocation, verifiers, signatures); + installLocation, verifiers, signatures, multiArch); } /** diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java index c6f98dc21330..fc2141f0c90d 100644 --- a/core/java/android/hardware/camera2/CameraCaptureSession.java +++ b/core/java/android/hardware/camera2/CameraCaptureSession.java @@ -93,9 +93,10 @@ public abstract class CameraCaptureSession implements AutoCloseable { * @throws IllegalStateException if this session is no longer active, either because the session * was explicitly closed, a new session has been created * or the camera device has been closed. - * @throws IllegalArgumentException if the request targets Surfaces that are not configured as - * outputs for this session. Or if the handler is null, the - * listener is not null, and the calling thread has no looper. + * @throws IllegalArgumentException if the request targets no Surfaces or Surfaces that are not + * configured as outputs for this session. Or if the handler is + * null, the listener is not null, and the calling thread has + * no looper. * * @see #captureBurst * @see #setRepeatingRequest @@ -137,9 +138,9 @@ public abstract class CameraCaptureSession implements AutoCloseable { * @throws IllegalStateException if this session is no longer active, either because the session * was explicitly closed, a new session has been created * or the camera device has been closed. - * @throws IllegalArgumentException If the requests target Surfaces not currently configured as - * outputs. Or if the handler is null, the listener is not - * null, and the calling thread has no looper. + * @throws IllegalArgumentException If the requests target no Surfaces or Surfaces not currently + * configured as outputs. Or if the handler is null, the + * listener is not null, and the calling thread has no looper. * * @see #capture * @see #setRepeatingRequest @@ -191,10 +192,10 @@ public abstract class CameraCaptureSession implements AutoCloseable { * @throws IllegalStateException if this session is no longer active, either because the session * was explicitly closed, a new session has been created * or the camera device has been closed. - * @throws IllegalArgumentException If the requests reference Surfaces that are not currently - * configured as outputs. Or if the handler is null, the - * listener is not null, and the calling thread has no looper. - * Or if no requests were passed in. + * @throws IllegalArgumentException If the requests reference no Surfaces or Surfaces that are + * not currently configured as outputs. Or if the handler is + * null, the listener is not null, and the calling thread has + * no looper. Or if no requests were passed in. * * @see #capture * @see #captureBurst @@ -251,10 +252,10 @@ public abstract class CameraCaptureSession implements AutoCloseable { * @throws IllegalStateException if this session is no longer active, either because the session * was explicitly closed, a new session has been created * or the camera device has been closed. - * @throws IllegalArgumentException If the requests reference Surfaces not currently configured - * as outputs. Or if the handler is null, the listener is not - * null, and the calling thread has no looper. Or if no - * requests were passed in. + * @throws IllegalArgumentException If the requests reference no Surfaces or Surfaces not + * currently configured as outputs. Or if the handler is null, + * the listener is not null, and the calling thread has no + * looper. Or if no requests were passed in. * * @see #capture * @see #captureBurst diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index f35a8199c39e..4b1659f622af 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -862,6 +862,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * devices, but the application should query this field to be sure.</p> * * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + * @see #REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE * @see #REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR * @see #REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING * @see #REQUEST_AVAILABLE_CAPABILITIES_DNG @@ -1679,14 +1680,17 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p>This tag specifies the zero light value for each of the CFA mosaic * channels in the camera sensor. The maximal value output by the * sensor is represented by the value in {@link CameraCharacteristics#SENSOR_INFO_WHITE_LEVEL android.sensor.info.whiteLevel}.</p> - * <p>The values are given in row-column scan order, with the first value - * corresponding to the element of the CFA in row=0, column=0.</p> + * <p>The values are given in the same order as channels listed for the CFA + * layout tag (see {@link CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT android.sensor.info.colorFilterArrangement}), i.e. the + * nth value given corresponds to the black level offset for the nth + * color channel listed in the CFA.</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * + * @see CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT * @see CameraCharacteristics#SENSOR_INFO_WHITE_LEVEL */ - public static final Key<int[]> SENSOR_BLACK_LEVEL_PATTERN = - new Key<int[]>("android.sensor.blackLevelPattern", int[].class); + public static final Key<android.hardware.camera2.params.BlackLevelPattern> SENSOR_BLACK_LEVEL_PATTERN = + new Key<android.hardware.camera2.params.BlackLevelPattern>("android.sensor.blackLevelPattern", android.hardware.camera2.params.BlackLevelPattern.class); /** * <p>Maximum sensitivity that is implemented diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 6c3f7ddaf1b0..fa35f446536b 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -217,28 +217,18 @@ public abstract class CameraMetadata<TKey> { /** * <p>The minimal set of capabilities that every camera * device (regardless of {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel}) - * will support.</p> - * <p>The full set of features supported by this capability makes - * the camera2 api backwards compatible with the camera1 - * (android.hardware.Camera) API.</p> + * supports.</p> + * <p>This capability is listed by all devices, and + * indicates that the camera device has a feature set + * that's comparable to the baseline requirements for the + * older android.hardware.Camera API.</p> * * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES - * @hide */ public static final int REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE = 0; /** - * <p>This is a catch-all capability to include all other - * tags or functionality not encapsulated by one of the other - * capabilities.</p> - * <p>A typical example is all tags marked 'optional'.</p> - * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES - * @hide - */ - public static final int REQUEST_AVAILABLE_CAPABILITIES_OPTIONAL = 1; - - /** * <p>The camera device can be manually controlled (3A algorithms such * as auto-exposure, and auto-focus can be bypassed). * The camera device supports basic manual control of the sensor image @@ -292,7 +282,7 @@ public abstract class CameraMetadata<TKey> { * @see CaptureRequest#SENSOR_SENSITIVITY * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES */ - public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR = 2; + public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR = 1; /** * <p>The camera device post-processing stages can be manually controlled. @@ -339,7 +329,26 @@ public abstract class CameraMetadata<TKey> { * @see CaptureRequest#TONEMAP_MODE * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES */ - public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING = 3; + public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING = 2; + + /** + * <p>The camera device supports outputting RAW buffers that can be + * saved offline into a DNG format.</p> + * <ul> + * <li>RAW_SENSOR is supported as an output format.</li> + * <li>The maximum available resolution for RAW_SENSOR streams + * will match either the value in + * {@link CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE android.sensor.info.pixelArraySize} or + * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.</li> + * <li>All DNG-related optional metadata entries are provided + * by the camera device.</li> + * </ul> + * + * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE + * @see CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + */ + public static final int REQUEST_AVAILABLE_CAPABILITIES_DNG = 3; /** * <p>The camera device supports the Zero Shutter Lag use case.</p> @@ -360,29 +369,6 @@ public abstract class CameraMetadata<TKey> { */ public static final int REQUEST_AVAILABLE_CAPABILITIES_ZSL = 4; - /** - * <p>The camera device supports outputting RAW buffers that can be - * saved offline into a DNG format. It can reprocess DNG - * files (produced from the same camera device) back into YUV.</p> - * <ul> - * <li>At least one input stream can be used.</li> - * <li>RAW16 is supported as output/input format.</li> - * <li>RAW16 is reprocessable into both YUV_420_888 and JPEG - * formats.</li> - * <li>The maximum available resolution for RAW16 streams (both - * input/output) will match either the value in - * {@link CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE android.sensor.info.pixelArraySize} or - * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.</li> - * <li>All DNG-related optional metadata entries are provided - * by the camera device.</li> - * </ul> - * - * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE - * @see CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE - * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES - */ - public static final int REQUEST_AVAILABLE_CAPABILITIES_DNG = 5; - // // Enumeration values for CameraCharacteristics#SCALER_CROPPING_TYPE // diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index e8014b012364..964f4f3c171c 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -446,6 +446,12 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> /** * Build a request using the current target Surfaces and settings. + * <p>Note that, although it is possible to create a {@code CaptureRequest} with no target + * {@link Surface}s, passing such a request into {@link CameraCaptureSession#capture}, + * {@link CameraCaptureSession#captureBurst}, + * {@link CameraCaptureSession#setRepeatingBurst}, or + * {@link CameraCaptureSession#setRepeatingRequest} will cause that method to throw an + * {@link IllegalArgumentException}.</p> * * @return A new capture request instance, ready for submission to the * camera device. diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java index 83aee5d171d1..6de5c25934c7 100644 --- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java +++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java @@ -27,6 +27,7 @@ import android.hardware.camera2.marshal.MarshalQueryable; import android.hardware.camera2.marshal.MarshalRegistry; import android.hardware.camera2.marshal.impl.MarshalQueryableArray; import android.hardware.camera2.marshal.impl.MarshalQueryableBoolean; +import android.hardware.camera2.marshal.impl.MarshalQueryableBlackLevelPattern; import android.hardware.camera2.marshal.impl.MarshalQueryableColorSpaceTransform; import android.hardware.camera2.marshal.impl.MarshalQueryableEnum; import android.hardware.camera2.marshal.impl.MarshalQueryableMeteringRectangle; @@ -1013,6 +1014,7 @@ public class CameraMetadataNative implements Parcelable { new MarshalQueryableStreamConfiguration(), new MarshalQueryableStreamConfigurationDuration(), new MarshalQueryableRggbChannelVector(), + new MarshalQueryableBlackLevelPattern(), // generic parcelable marshaler (MUST BE LAST since it has lowest priority) new MarshalQueryableParcelable(), diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableBlackLevelPattern.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableBlackLevelPattern.java new file mode 100644 index 000000000000..bcb035eadb5d --- /dev/null +++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableBlackLevelPattern.java @@ -0,0 +1,76 @@ +/* + * 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.hardware.camera2.marshal.impl; + +import android.hardware.camera2.marshal.MarshalQueryable; +import android.hardware.camera2.marshal.Marshaler; +import android.hardware.camera2.params.BlackLevelPattern; +import android.hardware.camera2.utils.TypeReference; + +import java.nio.ByteBuffer; + +import static android.hardware.camera2.impl.CameraMetadataNative.TYPE_INT32; +import static android.hardware.camera2.marshal.MarshalHelpers.SIZEOF_INT32; + +/** + * Marshal {@link BlackLevelPattern} to/from {@link #TYPE_INT32} {@code x 4} + */ +public class MarshalQueryableBlackLevelPattern implements MarshalQueryable<BlackLevelPattern> { + private static final int SIZE = SIZEOF_INT32 * BlackLevelPattern.COUNT; + + private class MarshalerBlackLevelPattern extends Marshaler<BlackLevelPattern> { + protected MarshalerBlackLevelPattern(TypeReference<BlackLevelPattern> typeReference, + int nativeType) { + super(MarshalQueryableBlackLevelPattern.this, typeReference, nativeType); + } + + @Override + public void marshal(BlackLevelPattern value, ByteBuffer buffer) { + for (int i = 0; i < BlackLevelPattern.COUNT / 2; ++i) { + for (int j = 0; j < BlackLevelPattern.COUNT / 2; ++j) { + buffer.putInt(value.getOffsetForIndex(j, i)); + } + } + } + + @Override + public BlackLevelPattern unmarshal(ByteBuffer buffer) { + int[] channelOffsets = new int[BlackLevelPattern.COUNT]; + for (int i = 0; i < BlackLevelPattern.COUNT; ++i) { + channelOffsets[i] = buffer.getInt(); + } + return new BlackLevelPattern(channelOffsets); + } + + @Override + public int getNativeSize() { + return SIZE; + } + } + + @Override + public Marshaler<BlackLevelPattern> createMarshaler( + TypeReference<BlackLevelPattern> managedType, int nativeType) { + return new MarshalerBlackLevelPattern(managedType, nativeType); + } + + @Override + public boolean isTypeMappingSupported( + TypeReference<BlackLevelPattern> managedType, int nativeType) { + return nativeType == TYPE_INT32 && + (BlackLevelPattern.class.equals(managedType.getType())); + } +} diff --git a/core/java/android/hardware/camera2/params/BlackLevelPattern.java b/core/java/android/hardware/camera2/params/BlackLevelPattern.java new file mode 100644 index 000000000000..a09f3d97c5a6 --- /dev/null +++ b/core/java/android/hardware/camera2/params/BlackLevelPattern.java @@ -0,0 +1,128 @@ +/* + * 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.hardware.camera2.params; + +import java.util.Arrays; + +import static com.android.internal.util.Preconditions.checkNotNull; + +/** + * Immutable class to store a 4-element vector of integers corresponding to a 2x2 pattern + * of color channel offsets used for the black level offsets of each color channel. + */ +public final class BlackLevelPattern { + + /** + * The number of offsets in this vector. + */ + public static final int COUNT = 4; + + /** + * Create a new {@link BlackLevelPattern} from a given offset array. + * + * <p>The given offset array must contain offsets for each color channel in + * a 2x2 pattern corresponding to the color filter arrangement. Offsets are + * given in row-column scan order.</p> + * + * @param offsets an array containing a 2x2 pattern of offsets. + * + * @throws IllegalArgumentException if the given array has an incorrect length. + * @throws NullPointerException if the given array is null. + * @hide + */ + public BlackLevelPattern(int[] offsets) { + if (offsets == null) { + throw new NullPointerException("Null offsets array passed to constructor"); + } + if (offsets.length < COUNT) { + throw new IllegalArgumentException("Invalid offsets array length"); + } + mCfaOffsets = Arrays.copyOf(offsets, COUNT); + } + + /** + * Return the color channel offset for a given index into the array of raw pixel values. + * + * @param column the column index in the the raw pixel array. + * @param row the row index in the raw pixel array. + * @return a color channel offset. + * + * @throws IllegalArgumentException if a column or row given is negative. + */ + public int getOffsetForIndex(int column, int row) { + if (row < 0 || column < 0) { + throw new IllegalArgumentException("column, row arguments must be positive"); + } + return mCfaOffsets[((row & 1) << 1) | (column & 1)]; + } + + /** + * Copy the ColorChannel offsets into the destination vector. + * + * <p>Offsets are given in row-column scan order for a given 2x2 color pattern.</p> + * + * @param destination an array big enough to hold at least {@value #COUNT} elements after the + * {@code offset} + * @param offset a non-negative offset into the array + * + * @throws IllegalArgumentException if the offset is invalid. + * @throws ArrayIndexOutOfBoundsException if the destination vector is too small. + * @throws NullPointerException if the destination is null. + */ + public void copyTo(int[] destination, int offset) { + checkNotNull(destination, "destination must not be null"); + if (offset < 0) { + throw new IllegalArgumentException("Null offset passed to copyTo"); + } + if (destination.length - offset < COUNT) { + throw new ArrayIndexOutOfBoundsException("destination too small to fit elements"); + } + for (int i = 0; i < COUNT; ++i) { + destination[offset + i] = mCfaOffsets[i]; + } + } + + /** + * Check if this {@link BlackLevelPattern} is equal to another {@link BlackLevelPattern}. + * + * <p>Two vectors are only equal if and only if each of the respective elements is equal.</p> + * + * @return {@code true} if the objects were equal, {@code false} otherwise + */ + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } else if (this == obj) { + return true; + } else if (obj instanceof BlackLevelPattern) { + final BlackLevelPattern other = (BlackLevelPattern) obj; + return Arrays.equals(other.mCfaOffsets, mCfaOffsets); + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Arrays.hashCode(mCfaOffsets); + } + + private final int[] mCfaOffsets; +} diff --git a/core/java/android/hardware/camera2/params/RggbChannelVector.java b/core/java/android/hardware/camera2/params/RggbChannelVector.java index 30591f653e0d..cf3e1dec9dc7 100644 --- a/core/java/android/hardware/camera2/params/RggbChannelVector.java +++ b/core/java/android/hardware/camera2/params/RggbChannelVector.java @@ -146,7 +146,7 @@ public final class RggbChannelVector { */ public void copyTo(final float[] destination, final int offset) { checkNotNull(destination, "destination must not be null"); - if (destination.length + offset < COUNT) { + if (destination.length - offset < COUNT) { throw new ArrayIndexOutOfBoundsException("destination too small to fit elements"); } @@ -167,11 +167,9 @@ public final class RggbChannelVector { public boolean equals(final Object obj) { if (obj == null) { return false; - } - if (this == obj) { + } else if (this == obj) { return true; - } - if (obj instanceof RggbChannelVector) { + } else if (obj instanceof RggbChannelVector) { final RggbChannelVector other = (RggbChannelVector) obj; return mRed == other.mRed && mGreenEven == other.mGreenEven && diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java index fff171b92c1e..8385d638ec25 100644 --- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java +++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java @@ -357,6 +357,10 @@ public final class StreamConfigurationMap { * <p>When multiple streams are used in a request, the minimum frame duration will be * {@code max(individual stream min durations)}.</p> * + * <p>For devices that do not support manual sensor control + * ({@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR}), + * this function may return {@link #NO_MIN_FRAME_DURATION}.</p> + * * <!-- * TODO: uncomment after adding input stream support * <p>The minimum frame duration of a stream (of a particular format, size) is the same @@ -366,8 +370,7 @@ public final class StreamConfigurationMap { * @param format an image format from {@link ImageFormat} or {@link PixelFormat} * @param size an output-compatible size * @return a minimum frame duration {@code >} 0 in nanoseconds, or - * {@link #NO_MIN_FRAME_DURATION} if the minimum frame duration is not available (this - * can only occur on limited mode devices). + * {@link #NO_MIN_FRAME_DURATION} if the minimum frame duration is not available. * * @throws IllegalArgumentException if {@code format} or {@code size} was not supported * @throws NullPointerException if {@code size} was {@code null} @@ -404,6 +407,10 @@ public final class StreamConfigurationMap { * <p>When multiple streams are used in a request, the minimum frame duration will be * {@code max(individual stream min durations)}.</p> * + * <p>For devices that do not support manual sensor control + * ({@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR}), + * this function may return {@link #NO_MIN_FRAME_DURATION}.</p> + * * <!-- * TODO: uncomment after adding input stream support * <p>The minimum frame duration of a stream (of a particular format, size) is the same @@ -415,8 +422,7 @@ public final class StreamConfigurationMap { * non-empty array returned by {@link #getOutputSizes(Class)} * @param size an output-compatible size * @return a minimum frame duration {@code >} 0 in nanoseconds, or - * {@link #NO_MIN_FRAME_DURATION} if the minimum frame duration is not available (this - * can only occur on limited mode devices). + * {@link #NO_MIN_FRAME_DURATION} if the minimum frame duration is not available. * * @throws IllegalArgumentException if {@code klass} or {@code size} was not supported * @throws NullPointerException if {@code size} or {@code klass} was {@code null} diff --git a/core/java/android/hardware/hdmi/HdmiClient.java b/core/java/android/hardware/hdmi/HdmiClient.java index bb0f4ba539c6..c43e21acc344 100644 --- a/core/java/android/hardware/hdmi/HdmiClient.java +++ b/core/java/android/hardware/hdmi/HdmiClient.java @@ -24,6 +24,29 @@ public abstract class HdmiClient { mService = service; } + /** + * Send a key event to other logical device. + * + * @param keyCode key code to send. Defined in {@link android.view.KeyEvent}. + * @param isPressed true if this is key press event + */ + public void sendKeyEvent(int keyCode, boolean isPressed) { + try { + mService.sendKeyEvent(getDeviceType(), keyCode, isPressed); + } catch (RemoteException e) { + Log.e(TAG, "queryDisplayStatus threw exception ", e); + } + } + + /** + * Send vendor-specific command. + * + * @param targetAddress address of the target device + * @param params vendor-specific parameter. For <Vendor Command With ID> do not + * include the first 3 bytes (vendor ID). + * @param hasVendorId {@code true} if the command type will be <Vendor Command With ID>. + * {@code false} if the command will be <Vendor Command> + */ public void sendVendorCommand(int targetAddress, byte[] params, boolean hasVendorId) { try { mService.sendVendorCommand(getDeviceType(), targetAddress, params, hasVendorId); @@ -32,6 +55,11 @@ public abstract class HdmiClient { } } + /** + * Add a listener used to receive incoming vendor-specific command. + * + * @param listener listener object + */ public void addVendorCommandListener(VendorCommandListener listener) { try { mService.addVendorCommandListener(getListenerWrapper(listener), getDeviceType()); diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl index 3a477fbe1613..f3931e357a2a 100644 --- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl +++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl @@ -43,7 +43,7 @@ interface IHdmiControlService { void addDeviceEventListener(IHdmiDeviceEventListener listener); void deviceSelect(int logicalAddress, IHdmiControlCallback callback); void portSelect(int portId, IHdmiControlCallback callback); - void sendKeyEvent(int keyCode, boolean isPressed); + void sendKeyEvent(int deviceType, int keyCode, boolean isPressed); List<HdmiPortInfo> getPortInfo(); boolean canChangeSystemAudioMode(); boolean getSystemAudioMode(); diff --git a/core/java/android/hardware/location/GeofenceHardware.java b/core/java/android/hardware/location/GeofenceHardware.java index 4c074e992919..2d7b7e1d5831 100644 --- a/core/java/android/hardware/location/GeofenceHardware.java +++ b/core/java/android/hardware/location/GeofenceHardware.java @@ -56,8 +56,6 @@ public final class GeofenceHardware { /** * Constant for geofence monitoring done by the Fused hardware. - * - * @hide */ public static final int MONITORING_TYPE_FUSED_HARDWARE = 1; @@ -128,8 +126,6 @@ public final class GeofenceHardware { /** * The constant used to indicate that the operation failed due to insufficient memory. - * - * @hide */ public static final int GEOFENCE_ERROR_INSUFFICIENT_MEMORY = 6; diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index fb4912f27022..a7e03fc7b40f 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -743,7 +743,7 @@ public class ConnectivityManager { * network type or {@code null} if the type is not * supported by the device. * - * <p>This method requires the call to hold the permission + * <p>This method requires the caller to hold the permission * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}. */ public NetworkInfo getNetworkInfo(int networkType) { @@ -755,13 +755,34 @@ public class ConnectivityManager { } /** + * Returns connection status information about a particular + * Network. + * + * @param network {@link Network} specifying which network + * in which you're interested. + * @return a {@link NetworkInfo} object for the requested + * network or {@code null} if the {@code Network} + * is not valid. + * + * <p>This method requires the caller to hold the permission + * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}. + */ + public NetworkInfo getNetworkInfo(Network network) { + try { + return mService.getNetworkInfoForNetwork(network); + } catch (RemoteException e) { + return null; + } + } + + /** * Returns connection status information about all network * types supported by the device. * * @return an array of {@link NetworkInfo} objects. Check each * {@link NetworkInfo#getType} for which type each applies. * - * <p>This method requires the call to hold the permission + * <p>This method requires the caller to hold the permission * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}. */ public NetworkInfo[] getAllNetworkInfo() { @@ -773,6 +794,23 @@ public class ConnectivityManager { } /** + * Returns an array of all {@link Network} currently tracked by the + * framework. + * + * @return an array of {@link Network} objects. + * + * <p>This method requires the caller to hold the permission + * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}. + */ + public Network[] getAllNetworks() { + try { + return mService.getAllNetworks(); + } catch (RemoteException e) { + return null; + } + } + + /** * Returns details about the Provisioning or currently active default data network. When * connected, this network is the default route for outgoing connections. * You should always check {@link NetworkInfo#isConnected()} before initiating @@ -1462,6 +1500,20 @@ public class ConnectivityManager { } /** + * Get the set of tethered dhcp ranges. + * + * @return an array of 0 or more {@code String} of tethered dhcp ranges. + * {@hide} + */ + public String[] getTetheredDhcpRanges() { + try { + return mService.getTetheredDhcpRanges(); + } catch (RemoteException e) { + return new String[0]; + } + } + + /** * Attempt to tether the named interface. This will setup a dhcp server * on the interface, forward and NAT IP packets and forward DNS requests * to the best active upstream network interface. Note that if no upstream diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 51d9b8ca45f1..b9c6491da2e1 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -48,7 +48,9 @@ interface IConnectivityManager NetworkInfo getActiveNetworkInfo(); NetworkInfo getActiveNetworkInfoForUid(int uid); NetworkInfo getNetworkInfo(int networkType); + NetworkInfo getNetworkInfoForNetwork(in Network network); NetworkInfo[] getAllNetworkInfo(); + Network[] getAllNetworks(); NetworkInfo getProvisioningOrActiveNetworkInfo(); @@ -91,6 +93,8 @@ interface IConnectivityManager String[] getTetheringErroredIfaces(); + String[] getTetheredDhcpRanges(); + String[] getTetherableUsbRegexs(); String[] getTetherableWifiRegexs(); @@ -111,8 +115,6 @@ interface IConnectivityManager void setDataDependency(int networkType, boolean met); - boolean protectVpn(in ParcelFileDescriptor socket); - boolean prepareVpn(String oldPackage, String newPackage); ParcelFileDescriptor establishVpn(in VpnConfig config); diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java index 3d0874bedae0..41eab021ee67 100644 --- a/core/java/android/net/NetworkAgent.java +++ b/core/java/android/net/NetworkAgent.java @@ -92,6 +92,20 @@ public abstract class NetworkAgent extends Handler { */ public static final int EVENT_NETWORK_SCORE_CHANGED = BASE + 4; + /** + * Sent by the NetworkAgent to ConnectivityService to add new UID ranges + * to be forced into this Network. For VPNs only. + * obj = UidRange[] to forward + */ + public static final int EVENT_UID_RANGES_ADDED = BASE + 5; + + /** + * Sent by the NetworkAgent to ConnectivityService to remove UID ranges + * from being forced into this Network. For VPNs only. + * obj = UidRange[] to stop forwarding + */ + public static final int EVENT_UID_RANGES_REMOVED = BASE + 6; + public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni, NetworkCapabilities nc, LinkProperties lp, int score) { super(looper); @@ -194,6 +208,22 @@ public abstract class NetworkAgent extends Handler { } /** + * Called by the VPN code when it wants to add ranges of UIDs to be routed + * through the VPN network. + */ + public void addUidRanges(UidRange[] ranges) { + queueOrSendMessage(EVENT_UID_RANGES_ADDED, ranges); + } + + /** + * Called by the VPN code when it wants to remove ranges of UIDs from being routed + * through the VPN network. + */ + public void removeUidRanges(UidRange[] ranges) { + queueOrSendMessage(EVENT_UID_RANGES_REMOVED, ranges); + } + + /** * Called when ConnectivityService has indicated they no longer want this network. * The parent factory should (previously) have received indication of the change * as well, either canceling NetworkRequests or altering their score such that this diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index 00200d0f42ca..53f9fcdd93c0 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -18,6 +18,7 @@ package android.net; import android.os.Parcel; import android.os.Parcelable; +import android.text.TextUtils; import android.util.Log; import java.lang.IllegalArgumentException; @@ -56,6 +57,7 @@ public final class NetworkCapabilities implements Parcelable { mTransportTypes = nc.mTransportTypes; mLinkUpBandwidthKbps = nc.mLinkUpBandwidthKbps; mLinkDownBandwidthKbps = nc.mLinkDownBandwidthKbps; + mNetworkSpecifier = nc.mNetworkSpecifier; } } @@ -64,7 +66,7 @@ public final class NetworkCapabilities implements Parcelable { * by any Network that matches all of them. */ private long mNetworkCapabilities = (1 << NET_CAPABILITY_NOT_RESTRICTED) | - (1 << NET_CAPABILITY_TRUSTED); + (1 << NET_CAPABILITY_TRUSTED) | (1 << NET_CAPABILITY_NOT_VPN); /** * Indicates this is a network that has the ability to reach the @@ -158,9 +160,15 @@ public final class NetworkCapabilities implements Parcelable { */ public static final int NET_CAPABILITY_TRUSTED = 14; + /* + * Indicates that this network is not a VPN. This capability is set by default and should be + * explicitly cleared when creating VPN networks. + */ + public static final int NET_CAPABILITY_NOT_VPN = 15; + private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS; - private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_TRUSTED; + private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_NOT_VPN; /** * Adds the given capability to this {@code NetworkCapability} instance. @@ -271,8 +279,13 @@ public final class NetworkCapabilities implements Parcelable { */ public static final int TRANSPORT_ETHERNET = 3; + /** + * Indicates this network uses a VPN transport. + */ + public static final int TRANSPORT_VPN = 4; + private static final int MIN_TRANSPORT = TRANSPORT_CELLULAR; - private static final int MAX_TRANSPORT = TRANSPORT_ETHERNET; + private static final int MAX_TRANSPORT = TRANSPORT_VPN; /** * Adds the given transport type to this {@code NetworkCapability} instance. @@ -292,6 +305,7 @@ public final class NetworkCapabilities implements Parcelable { throw new IllegalArgumentException("TransportType out of range"); } mTransportTypes |= 1 << transportType; + setNetworkSpecifier(mNetworkSpecifier); // used for exception checking return this; } @@ -307,6 +321,7 @@ public final class NetworkCapabilities implements Parcelable { throw new IllegalArgumentException("TransportType out of range"); } mTransportTypes &= ~(1 << transportType); + setNetworkSpecifier(mNetworkSpecifier); // used for exception checking return this; } @@ -426,6 +441,62 @@ public final class NetworkCapabilities implements Parcelable { this.mLinkDownBandwidthKbps == nc.mLinkDownBandwidthKbps); } + private String mNetworkSpecifier; + /** + * Sets the optional bearer specific network specifier. + * This has no meaning if a single transport is also not specified, so calling + * this without a single transport set will generate an exception, as will + * subsequently adding or removing transports after this is set. + * </p> + * The interpretation of this {@code String} is bearer specific and bearers that use + * it should document their particulars. For example, Bluetooth may use some sort of + * device id while WiFi could used SSID and/or BSSID. Cellular may use carrier SPN (name) + * or Subscription ID. + * + * @param networkSpecifier An {@code String} of opaque format used to specify the bearer + * specific network specifier where the bearer has a choice of + * networks. + * @hide + */ + public void setNetworkSpecifier(String networkSpecifier) { + if (TextUtils.isEmpty(networkSpecifier) == false && Long.bitCount(mTransportTypes) != 1) { + throw new IllegalStateException("Must have a single transport specified to use " + + "setNetworkSpecifier"); + } + mNetworkSpecifier = networkSpecifier; + } + + /** + * Gets the optional bearer specific network specifier. + * + * @return The optional {@code String} specifying the bearer specific network specifier. + * See {@link #setNetworkSpecifier}. + * @hide + */ + public String getNetworkSpecifier() { + return mNetworkSpecifier; + } + + private void combineSpecifiers(NetworkCapabilities nc) { + String otherSpecifier = nc.getNetworkSpecifier(); + if (TextUtils.isEmpty(otherSpecifier)) return; + if (TextUtils.isEmpty(mNetworkSpecifier) == false) { + throw new IllegalStateException("Can't combine two networkSpecifiers"); + } + setNetworkSpecifier(otherSpecifier); + } + private boolean satisfiedBySpecifier(NetworkCapabilities nc) { + return (TextUtils.isEmpty(mNetworkSpecifier) || + mNetworkSpecifier.equals(nc.mNetworkSpecifier)); + } + private boolean equalsSpecifier(NetworkCapabilities nc) { + if (TextUtils.isEmpty(mNetworkSpecifier)) { + return TextUtils.isEmpty(nc.mNetworkSpecifier); + } else { + return mNetworkSpecifier.equals(nc.mNetworkSpecifier); + } + } + /** * Combine a set of Capabilities to this one. Useful for coming up with the complete set * {@hide} @@ -434,6 +505,7 @@ public final class NetworkCapabilities implements Parcelable { combineNetCapabilities(nc); combineTransportTypes(nc); combineLinkBandwidths(nc); + combineSpecifiers(nc); } /** @@ -444,7 +516,8 @@ public final class NetworkCapabilities implements Parcelable { return (nc != null && satisfiedByNetCapabilities(nc) && satisfiedByTransportTypes(nc) && - satisfiedByLinkBandwidths(nc)); + satisfiedByLinkBandwidths(nc) && + satisfiedBySpecifier(nc)); } @Override @@ -453,7 +526,8 @@ public final class NetworkCapabilities implements Parcelable { NetworkCapabilities that = (NetworkCapabilities)obj; return (equalsNetCapabilities(that) && equalsTransportTypes(that) && - equalsLinkBandwidths(that)); + equalsLinkBandwidths(that) && + equalsSpecifier(that)); } @Override @@ -463,7 +537,8 @@ public final class NetworkCapabilities implements Parcelable { ((int)(mTransportTypes & 0xFFFFFFFF) * 5) + ((int)(mTransportTypes >> 32) * 7) + (mLinkUpBandwidthKbps * 11) + - (mLinkDownBandwidthKbps * 13)); + (mLinkDownBandwidthKbps * 13) + + (TextUtils.isEmpty(mNetworkSpecifier) ? 0 : mNetworkSpecifier.hashCode() * 17)); } public int describeContents() { @@ -474,6 +549,7 @@ public final class NetworkCapabilities implements Parcelable { dest.writeLong(mTransportTypes); dest.writeInt(mLinkUpBandwidthKbps); dest.writeInt(mLinkDownBandwidthKbps); + dest.writeString(mNetworkSpecifier); } public static final Creator<NetworkCapabilities> CREATOR = new Creator<NetworkCapabilities>() { @@ -484,6 +560,7 @@ public final class NetworkCapabilities implements Parcelable { netCap.mTransportTypes = in.readLong(); netCap.mLinkUpBandwidthKbps = in.readInt(); netCap.mLinkDownBandwidthKbps = in.readInt(); + netCap.mNetworkSpecifier = in.readString(); return netCap; } public NetworkCapabilities[] newArray(int size) { @@ -500,6 +577,7 @@ public final class NetworkCapabilities implements Parcelable { case TRANSPORT_WIFI: transports += "WIFI"; break; case TRANSPORT_BLUETOOTH: transports += "BLUETOOTH"; break; case TRANSPORT_ETHERNET: transports += "ETHERNET"; break; + case TRANSPORT_VPN: transports += "VPN"; break; } if (++i < types.length) transports += "|"; } @@ -523,6 +601,7 @@ public final class NetworkCapabilities implements Parcelable { case NET_CAPABILITY_INTERNET: capabilities += "INTERNET"; break; case NET_CAPABILITY_NOT_RESTRICTED: capabilities += "NOT_RESTRICTED"; break; case NET_CAPABILITY_TRUSTED: capabilities += "TRUSTED"; break; + case NET_CAPABILITY_NOT_VPN: capabilities += "NOT_VPN"; break; } if (++i < types.length) capabilities += "&"; } @@ -532,6 +611,9 @@ public final class NetworkCapabilities implements Parcelable { String dnBand = ((mLinkDownBandwidthKbps > 0) ? " LinkDnBandwidth>=" + mLinkDownBandwidthKbps + "Kbps" : ""); - return "[" + transports + capabilities + upBand + dnBand + "]"; + String specifier = (mNetworkSpecifier == null ? + "" : " Specifier: <" + mNetworkSpecifier + ">"); + + return "[" + transports + capabilities + upBand + dnBand + specifier + "]"; } } diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java index 36dc57398148..83bdfaab0286 100644 --- a/core/java/android/net/NetworkRequest.java +++ b/core/java/android/net/NetworkRequest.java @@ -153,6 +153,25 @@ public class NetworkRequest implements Parcelable { mNetworkCapabilities.setLinkDownstreamBandwidthKbps(downKbps); return this; } + + /** + * Sets the optional bearer specific network specifier. + * This has no meaning if a single transport is also not specified, so calling + * this without a single transport set will generate an exception, as will + * subsequently adding or removing transports after this is set. + * </p> + * The interpretation of this {@code String} is bearer specific and bearers that use + * it should document their particulars. For example, Bluetooth may use some sort of + * device id while WiFi could used ssid and/or bssid. Cellular may use carrier spn. + * + * @param networkSpecifier An {@code String} of opaque format used to specify the bearer + * specific network specifier where the bearer has a choice of + * networks. + */ + public Builder setNetworkSpecifier(String networkSpecifier) { + mNetworkCapabilities.setNetworkSpecifier(networkSpecifier); + return this; + } } // implement the Parcelable interface diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java index c4b17b604661..aa1e1233f1ef 100644 --- a/core/java/android/net/NetworkUtils.java +++ b/core/java/android/net/NetworkUtils.java @@ -155,6 +155,13 @@ public class NetworkUtils { public native static boolean bindSocketToNetwork(int socketfd, int netId); /** + * Protect {@code socketfd} from VPN connections. After protecting, data sent through + * this socket will go directly to the underlying network, so its traffic will not be + * forwarded through the VPN. + */ + public native static boolean protectFromVpn(int socketfd); + + /** * Convert a IPv4 address from an integer to an InetAddress. * @param hostAddress an int corresponding to the IPv4 address in network byte order */ diff --git a/core/java/android/net/UidRange.aidl b/core/java/android/net/UidRange.aidl new file mode 100644 index 000000000000..f9be628c1673 --- /dev/null +++ b/core/java/android/net/UidRange.aidl @@ -0,0 +1,24 @@ +/* + * 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.net; + +/** + * An inclusive range of UIDs. + * + * {@hide} + */ +parcelable UidRange; diff --git a/core/java/android/net/UidRange.java b/core/java/android/net/UidRange.java new file mode 100644 index 000000000000..2e586b39b5be --- /dev/null +++ b/core/java/android/net/UidRange.java @@ -0,0 +1,102 @@ +/* + * 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.net; + +import static android.os.UserHandle.PER_USER_RANGE; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.IllegalArgumentException; + +/** + * An inclusive range of UIDs. + * + * @hide + */ +public final class UidRange implements Parcelable { + public final int start; + public final int stop; + + public UidRange(int startUid, int stopUid) { + if (startUid < 0) throw new IllegalArgumentException("Invalid start UID."); + if (stopUid < 0) throw new IllegalArgumentException("Invalid stop UID."); + if (startUid > stopUid) throw new IllegalArgumentException("Invalid UID range."); + start = startUid; + stop = stopUid; + } + + public static UidRange createForUser(int userId) { + return new UidRange(userId * PER_USER_RANGE, (userId + 1) * PER_USER_RANGE - 1); + } + + public int getStartUser() { + return start / PER_USER_RANGE; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + start; + result = 31 * result + stop; + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o instanceof UidRange) { + UidRange other = (UidRange) o; + return start == other.start && stop == other.stop; + } + return false; + } + + @Override + public String toString() { + return start + "-" + stop; + } + + // implement the Parcelable interface + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(start); + dest.writeInt(stop); + } + + public static final Creator<UidRange> CREATOR = + new Creator<UidRange>() { + @Override + public UidRange createFromParcel(Parcel in) { + int start = in.readInt(); + int stop = in.readInt(); + + return new UidRange(start, stop); + } + @Override + public UidRange[] newArray(int size) { + return new UidRange[size]; + } + }; +} diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java index 7385dff3ff80..5d61de22e9ac 100644 --- a/core/java/android/net/VpnService.java +++ b/core/java/android/net/VpnService.java @@ -16,11 +16,16 @@ package android.net; +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_INET6; + import android.app.Activity; import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.NetworkUtils; import android.os.Binder; import android.os.IBinder; import android.os.Parcel; @@ -165,19 +170,7 @@ public class VpnService extends Service { * @return {@code true} on success. */ public boolean protect(int socket) { - ParcelFileDescriptor dup = null; - try { - dup = ParcelFileDescriptor.fromFd(socket); - return getService().protectVpn(dup); - } catch (Exception e) { - return false; - } finally { - try { - dup.close(); - } catch (Exception e) { - // ignore - } - } + return NetworkUtils.protectFromVpn(socket); } /** @@ -202,6 +195,52 @@ public class VpnService extends Service { } /** + * Adds a network address to the VPN interface. + * + * Both IPv4 and IPv6 addresses are supported. The VPN must already be established. Fails if the + * address is already in use or cannot be assigned to the interface for any other reason. + * + * Adding an address implicitly allows traffic from that address family (i.e., IPv4 or IPv6) to + * be routed over the VPN. @see Builder#allowFamily + * + * @throws {@link IllegalArgumentException} if the address is invalid. + * + * @param address The IP address (IPv4 or IPv6) to assign to the VPN interface. + * @param prefixLength The prefix length of the address. + * + * @return {@code true} on success. + * @see Builder#addAddress + */ + public boolean addAddress(InetAddress address, int prefixLength) { + // TODO + return true; + } + + /** + * Removes a network address from the VPN interface. + * + * Both IPv4 and IPv6 addresses are supported. The VPN must already be established. Fails if the + * address is not assigned to the VPN interface, or if it is the only address assigned (thus + * cannot be removed), or if the address cannot be removed for any other reason. + * + * After removing an address, if there are no addresses, routes or DNS servers of a particular + * address family (i.e., IPv4 or IPv6) configured on the VPN, that <b>DOES NOT</b> block that + * family from being routed. In other words, once an address family has been allowed, it stays + * allowed for the rest of the VPN's session. @see Builder#allowFamily + * + * @throws {@link IllegalArgumentException} if the address is invalid. + * + * @param address The IP address (IPv4 or IPv6) to assign to the VPN interface. + * @param prefixLength The prefix length of the address. + * + * @return {@code true} on success. + */ + public boolean removeAddress(InetAddress address, int prefixLength) { + // TODO + return true; + } + + /** * Return the communication interface to the service. This method returns * {@code null} on {@link Intent}s other than {@link #SERVICE_INTERFACE} * action. Applications overriding this method must identify the intent @@ -322,6 +361,9 @@ public class VpnService extends Service { * addresses are supported. At least one address must be set before * calling {@link #establish}. * + * Adding an address implicitly allows traffic from that address family + * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily + * * @throws IllegalArgumentException if the address is invalid. */ public Builder addAddress(InetAddress address, int prefixLength) { @@ -339,6 +381,9 @@ public class VpnService extends Service { * using a numeric address string. See {@link InetAddress} for the * definitions of numeric address formats. * + * Adding an address implicitly allows traffic from that address family + * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily + * * @throws IllegalArgumentException if the address is invalid. * @see #addAddress(InetAddress, int) */ @@ -350,6 +395,9 @@ public class VpnService extends Service { * Add a network route to the VPN interface. Both IPv4 and IPv6 * routes are supported. * + * Adding a route implicitly allows traffic from that address family + * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily + * * @throws IllegalArgumentException if the route is invalid. */ public Builder addRoute(InetAddress address, int prefixLength) { @@ -373,6 +421,9 @@ public class VpnService extends Service { * using a numeric address string. See {@link InetAddress} for the * definitions of numeric address formats. * + * Adding a route implicitly allows traffic from that address family + * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily + * * @throws IllegalArgumentException if the route is invalid. * @see #addRoute(InetAddress, int) */ @@ -385,6 +436,9 @@ public class VpnService extends Service { * addresses are supported. If none is set, the DNS servers of * the default network will be used. * + * Adding a server implicitly allows traffic from that address family + * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily + * * @throws IllegalArgumentException if the address is invalid. */ public Builder addDnsServer(InetAddress address) { @@ -403,6 +457,9 @@ public class VpnService extends Service { * using a numeric address string. See {@link InetAddress} for the * definitions of numeric address formats. * + * Adding a server implicitly allows traffic from that address family + * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily + * * @throws IllegalArgumentException if the address is invalid. * @see #addDnsServer(InetAddress) */ @@ -422,6 +479,95 @@ public class VpnService extends Service { } /** + * Allows traffic from the specified address family. + * + * By default, if no address, route or DNS server of a specific family (IPv4 or IPv6) is + * added to this VPN, then all outgoing traffic of that family is blocked. If any address, + * route or DNS server is added, that family is allowed. + * + * This method allows an address family to be unblocked even without adding an address, + * route or DNS server of that family. Traffic of that family will then typically + * fall-through to the underlying network if it's supported. + * + * {@code family} must be either {@code AF_INET} (for IPv4) or {@code AF_INET6} (for IPv6). + * {@link IllegalArgumentException} is thrown if it's neither. + * + * @param family The address family ({@code AF_INET} or {@code AF_INET6}) to allow. + * + * @return this {@link Builder} object to facilitate chaining of method calls. + */ + public Builder allowFamily(int family) { + // TODO + return this; + } + + /** + * Adds an application that's allowed to access the VPN connection. + * + * If this method is called at least once, only applications added through this method (and + * no others) are allowed access. Else (if this method is never called), all applications + * are allowed by default. + * + * A {@link Builder} may have only a set of allowed applications OR a set of disallowed + * ones, but not both. Calling this method after {@link #addDisallowedApplication} has + * already been called, or vice versa, will throw an {@link UnsupportedOperationException}. + * + * {@code packageName} must be the canonical name of a currently installed application. + * {@link PackageManager.NameNotFoundException} is thrown if there's no such application. + * + * @throws {@link PackageManager.NameNotFoundException} If the application isn't installed. + * + * @param packageName The full name (e.g.: "com.google.apps.contacts") of an application. + * + * @return this {@link Builder} object to facilitate chaining method calls. + */ + public Builder addAllowedApplication(String packageName) + throws PackageManager.NameNotFoundException { + // TODO + return this; + } + + /** + * Adds an application that's denied access to the VPN connection. + * + * By default, all applications are allowed access, except for those denied through this + * method. + * + * A {@link Builder} may have only a set of allowed applications OR a set of disallowed + * ones, but not both. Calling this method after {@link #addAllowedApplication} has already + * been called, or vice versa, will throw an {@link UnsupportedOperationException}. + * + * {@code packageName} must be the canonical name of a currently installed application. + * {@link PackageManager.NameNotFoundException} is thrown if there's no such application. + * + * @throws {@link PackageManager.NameNotFoundException} If the application isn't installed. + * + * @param packageName The full name (e.g.: "com.google.apps.contacts") of an application. + * + * @return this {@link Builder} object to facilitate chaining method calls. + */ + public Builder addDisallowedApplication(String packageName) + throws PackageManager.NameNotFoundException { + // TODO + return this; + } + + /** + * Allows all apps to bypass this VPN connection. + * + * By default, all traffic from apps is forwarded through the VPN interface and it is not + * possible for apps to side-step the VPN. If this method is called, apps may use methods + * such as {@link ConnectivityManager#setProcessDefaultNetwork} to instead send/receive + * directly over the underlying network or any other network they have permissions for. + * + * @return this {@link Builder} object to facilitate chaining of method calls. + */ + public Builder allowBypass() { + // TODO + return this; + } + + /** * Create a VPN interface using the parameters supplied to this * builder. The interface works on IP packets, and a file descriptor * is returned for the application to access them. Each read diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index d0a6f089f374..c22c4b65ed79 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -2076,7 +2076,7 @@ public abstract class BatteryStats implements Parcelable { if (val != 0) hasData = true; } if (hasData) { - dumpLine(pw, 0 /* uid */, category, USER_ACTIVITY_DATA, args); + dumpLine(pw, uid /* uid */, category, USER_ACTIVITY_DATA, args); } } diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl index eb9ba13ea662..d997e44e34ff 100644 --- a/core/java/android/os/INetworkManagementService.aidl +++ b/core/java/android/os/INetworkManagementService.aidl @@ -22,6 +22,7 @@ import android.net.INetworkManagementEventObserver; import android.net.LinkAddress; import android.net.NetworkStats; import android.net.RouteInfo; +import android.net.UidRange; import android.net.wifi.WifiConfiguration; import android.os.INetworkActivityListener; @@ -325,28 +326,14 @@ interface INetworkManagementService void setFirewallUidRule(int uid, boolean allow); /** - * Set all packets from users [uid_start,uid_end] to go through interface iface - * iface must already be set for marked forwarding by {@link setMarkedForwarding} + * Set all packets from users in ranges to go through VPN specified by netId. */ - void setUidRangeRoute(String iface, int uid_start, int uid_end, boolean forward_dns); + void addVpnUidRanges(int netId, in UidRange[] ranges); /** - * Clears the special routing rules for users [uid_start,uid_end] + * Clears the special VPN rules for users in ranges and VPN specified by netId. */ - void clearUidRangeRoute(String iface, int uid_start, int uid_end); - - /** - * Setup an interface for routing packets marked by {@link setUidRangeRoute} - * - * This sets up a dedicated routing table for packets marked for {@code iface} and adds - * source-NAT rules so that the marked packets have the correct source address. - */ - void setMarkedForwarding(String iface); - - /** - * Removes marked forwarding for an interface - */ - void clearMarkedForwarding(String iface); + void removeVpnUidRanges(int netId, in UidRange[] ranges); /** * Get the SO_MARK associated with routing packets for user {@code uid} @@ -410,9 +397,14 @@ interface INetworkManagementService boolean isNetworkActive(); /** - * Setup a new network. + * Setup a new physical network. + */ + void createPhysicalNetwork(int netId); + + /** + * Setup a new VPN. */ - void createNetwork(int netId); + void createVirtualNetwork(int netId, boolean hasDNS); /** * Remove a network. @@ -437,4 +429,14 @@ interface INetworkManagementService void setPermission(boolean internal, boolean changeNetState, in int[] uids); void clearPermission(in int[] uids); + + /** + * Allow UID to call protect(). + */ + void allowProtect(int uid); + + /** + * Deny UID from calling protect(). + */ + void denyProtect(int uid); } diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 86c749a8da4f..8caea250d0c8 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -1085,4 +1085,18 @@ public class Process { */ public boolean usingWrapper; } + + /** + * Kill all processes in a process group started for the given + * pid. + * @hide + */ + public static final native int killProcessGroup(int uid, int pid); + + /** + * Remove all process groups. Expected to be called when ActivityManager + * is restarted. + * @hide + */ + public static final native void removeAllProcessGroups(); } diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java index acc74d8c4a29..760f2a5b9ac3 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -20,6 +20,7 @@ package android.provider; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; +import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.provider.ContactsContract.CommonDataKinds.Callable; @@ -87,6 +88,27 @@ public class CallLog { public static final String ALLOW_VOICEMAILS_PARAM_KEY = "allow_voicemails"; /** + * An optional extra used with {@link #CONTENT_TYPE Calls.CONTENT_TYPE} and + * {@link Intent#ACTION_VIEW} to specify that the presented list of calls should be + * filtered for a particular call type. + * + * Applications implementing a call log UI should check for this extra, and display a + * filtered list of calls based on the specified call type. If not applicable within the + * application's UI, it should be silently ignored. + * + * <p> + * The following example brings up the call log, showing only missed calls. + * <pre> + * Intent intent = new Intent(Intent.ACTION_VIEW); + * intent.setType(CallLog.Calls.CONTENT_TYPE); + * intent.putExtra(CallLog.Calls.EXTRA_CALL_TYPE_FILTER, CallLog.Calls.MISSED_TYPE); + * startActivity(intent); + * </pre> + * </p> + */ + public static final String EXTRA_CALL_TYPE_FILTER = "extra_call_type_filter"; + + /** * Content uri used to access call log entries, including voicemail records. You must have * the READ_CALL_LOG and WRITE_CALL_LOG permissions to read and write to the call log. */ @@ -127,6 +149,18 @@ public class CallLog { public static final int VOICEMAIL_TYPE = 4; /** + * Bit-mask describing features of the call (e.g. video). + * + * <P>Type: INTEGER (int)</P> + */ + public static final String FEATURES = "features"; + + /** Call had no associated features (e.g. voice-only). */ + public static final int FEATURES_NONE = 0x0; + /** Call had video. */ + public static final int FEATURES_VIDEO = 0x1; + + /** * The phone number as the user entered it. * <P>Type: TEXT</P> */ @@ -180,6 +214,12 @@ public class CallLog { public static final String DURATION = "duration"; /** + * The data usage of the call in bytes. + * <P>Type: INTEGER (long)</P> + */ + public static final String DATA_USAGE = "data_usage"; + + /** * Whether or not the call has been acknowledged * <P>Type: INTEGER (boolean)</P> */ @@ -302,14 +342,18 @@ public class CallLog { * is set by the network and denotes the number presenting rules for * "allowed", "payphone", "restricted" or "unknown" * @param callType enumerated values for "incoming", "outgoing", or "missed" + * @param features features of the call (e.g. Video). * @param account The account object describing the provider of the call * @param start time stamp for the call in milliseconds * @param duration call duration in seconds + * @param dataUsage data usage for the call in bytes, null if data usage was not tracked for + * the call. * * {@hide} */ public static Uri addCall(CallerInfo ci, Context context, String number, - int presentation, int callType, PhoneAccount account, long start, int duration) { + int presentation, int callType, int features, PhoneAccount account, long start, + int duration, Long dataUsage) { final ContentResolver resolver = context.getContentResolver(); int numberPresentation = PRESENTATION_ALLOWED; @@ -346,8 +390,12 @@ public class CallLog { values.put(NUMBER, number); values.put(NUMBER_PRESENTATION, Integer.valueOf(numberPresentation)); values.put(TYPE, Integer.valueOf(callType)); + values.put(FEATURES, features); values.put(DATE, Long.valueOf(start)); values.put(DURATION, Long.valueOf(duration)); + if (dataUsage != null) { + values.put(DATA_USAGE, dataUsage); + } values.put(PHONE_ACCOUNT_COMPONENT_NAME, accountComponentString); values.put(PHONE_ACCOUNT_ID, accountId); values.put(NEW, Integer.valueOf(1)); diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index cfe926d2080b..93f834a097f2 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -1670,6 +1670,24 @@ public final class ContactsContract { */ public static final String CONTENT_VCARD_TYPE = "text/x-vcard"; + + /** + * Mimimal ID for corp contacts returned from + * {@link PhoneLookup#ENTERPRISE_CONTENT_FILTER_URI}. + * + * @hide + */ + public static long CORP_CONTACT_ID_BASE = 1000000000; // slightly smaller than 2 ** 30 + + /** + * Return TRUE if a contact ID is from the contacts provider on the corp profile. + * + * {@link PhoneLookup#ENTERPRISE_CONTENT_FILTER_URI} may return such a contact. + */ + public static boolean isCorpContactId(long contactId) { + return (contactId >= CORP_CONTACT_ID_BASE) && (contactId < Profile.MIN_ID); + } + /** * A sub-directory of a single contact that contains all of the constituent raw contact * {@link ContactsContract.Data} rows. This directory can be used either @@ -4842,20 +4860,15 @@ public final class ContactsContract { * <p> * If a result is from the corp profile, it makes the following changes to the data: * <ul> - * <li>The following columns will be set to null, as they don't make sense on a - * different profile: - * {@link #_ID}, - * {@link #PHOTO_ID}, - * {@link #PHOTO_FILE_ID}, - * {@link #LOOKUP_KEY}, - * {@link #CUSTOM_RINGTONE}, - * {@link #IN_VISIBLE_GROUP}, - * and {@link #IN_DEFAULT_DIRECTORY}. - * </li> * <li> * {@link #PHOTO_THUMBNAIL_URI} and {@link #PHOTO_URI} will be rewritten to special * URIs. Use {@link ContentResolver#openAssetFileDescriptor} or its siblings to * load pictures from them. + * {@link #PHOTO_ID} and {@link #PHOTO_FILE_ID} will be set to null. Do not use them. + * </li> + * <li> + * Corp contacts will get artificial {@link #_ID}s. In order to tell whether a contact + * is from the corp profile, use {@link ContactsContract.Contacts#isCorpContactId(long)}. * </li> * </ul> * <p> diff --git a/core/java/android/provider/SearchIndexableData.java b/core/java/android/provider/SearchIndexableData.java index 60bcc40a7a1f..7b9d1ea316b3 100644 --- a/core/java/android/provider/SearchIndexableData.java +++ b/core/java/android/provider/SearchIndexableData.java @@ -58,6 +58,12 @@ public abstract class SearchIndexableData { public String key; /** + * The UserID for the data (in a multi user context). This is application specific and -1 is the + * default non initialized value. + */ + public int userId = -1; + + /** * The class name associated with the data. Generally this is a Fragment class name for * referring where the data is coming from and for launching the associated Fragment for * displaying the data. This is used only when the data is provided "locally". @@ -147,6 +153,9 @@ public abstract class SearchIndexableData { sb.append("key: "); sb.append(key); sb.append(", "); + sb.append("userId: "); + sb.append(userId); + sb.append(", "); sb.append("className: "); sb.append(className); sb.append(", "); diff --git a/core/java/android/provider/SearchIndexablesContract.java b/core/java/android/provider/SearchIndexablesContract.java index a8b4cfb0be18..1b5f72a4e110 100644 --- a/core/java/android/provider/SearchIndexablesContract.java +++ b/core/java/android/provider/SearchIndexablesContract.java @@ -65,7 +65,7 @@ public class SearchIndexablesContract { public static final String NON_INDEXABLES_KEYS_PATH = SETTINGS + "/" + NON_INDEXABLES_KEYS; /** - * Indexable xml resources colums. + * Indexable xml resources columns. */ public static final String[] INDEXABLES_XML_RES_COLUMNS = new String[] { XmlResource.COLUMN_RANK, // 0 @@ -78,7 +78,7 @@ public class SearchIndexablesContract { }; /** - * Indexable xml resources colums indices. + * Indexable xml resources columns indices. */ public static final int COLUMN_INDEX_XML_RES_RANK = 0; public static final int COLUMN_INDEX_XML_RES_RESID = 1; @@ -89,7 +89,7 @@ public class SearchIndexablesContract { public static final int COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS = 6; /** - * Indexable raw data colums. + * Indexable raw data columns. */ public static final String[] INDEXABLES_RAW_COLUMNS = new String[] { RawData.COLUMN_RANK, // 0 @@ -105,10 +105,11 @@ public class SearchIndexablesContract { RawData.COLUMN_INTENT_TARGET_PACKAGE, // 10 RawData.COLUMN_INTENT_TARGET_CLASS, // 11 RawData.COLUMN_KEY, // 12 + RawData.COLUMN_USER_ID, // 13 }; /** - * Indexable raw data colums indices. + * Indexable raw data columns indices. */ public static final int COLUMN_INDEX_RAW_RANK = 0; public static final int COLUMN_INDEX_RAW_TITLE = 1; @@ -123,16 +124,17 @@ public class SearchIndexablesContract { public static final int COLUMN_INDEX_RAW_INTENT_TARGET_PACKAGE = 10; public static final int COLUMN_INDEX_RAW_INTENT_TARGET_CLASS = 11; public static final int COLUMN_INDEX_RAW_KEY = 12; + public static final int COLUMN_INDEX_RAW_USER_ID = 13; /** - * Indexable raw data colums. + * Indexable raw data columns. */ public static final String[] NON_INDEXABLES_KEYS_COLUMNS = new String[] { NonIndexableKey.COLUMN_KEY_VALUE // 0 }; /** - * Non indexable data keys colums indices. + * Non indexable data keys columns indices. */ public static final int COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE = 0; @@ -204,6 +206,11 @@ public class SearchIndexablesContract { * Key associated with the raw data. The key needs to be unique. */ public static final String COLUMN_KEY = "key"; + + /** + * UserId associated with the raw data. + */ + public static final String COLUMN_USER_ID = "user_id"; } /** diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 99e108fa2ebe..073979738fcf 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1832,7 +1832,10 @@ public final class Settings { /** * A formatted string of the next alarm that is set, or the empty string * if there is no alarm set. + * + * @deprecated Use {@link android.app.AlarmManager#getNextAlarmClock()}. */ + @Deprecated public static final String NEXT_ALARM_FORMATTED = "next_alarm_formatted"; /** @@ -5109,6 +5112,61 @@ public final class Settings { public static final String INSTALL_NON_MARKET_APPS = Secure.INSTALL_NON_MARKET_APPS; /** + * Whether HDMI control shall be enabled. If disabled, no CEC/MHL command will be + * sent or processed. (0 = false, 1 = true) + * @hide + */ + public static final String HDMI_CONTROL_ENABLED = "hdmi_control_enabled"; + + /** + * Whether HDMI system audio is enabled. If enabled, TV internal speaker is muted, + * and the output is redirected to AV Receiver connected via + * {@Global#HDMI_SYSTEM_AUDIO_OUTPUT}. + * @hide + */ + public static final String HDMI_SYSTEM_AUDIO_ENABLED = "hdmi_system_audio_enabled"; + + /** + * Output of the audio to be used for system audio mode, as defined in AudioSystem.java. + * <ul> + * <li>DEVICE_OUT_SPDIF</li> + * <li>DEVICE_OUT_HDMI_ARC</li> + * <li>DEVICE_OUT_LINE</li> + * </ul> + * @hide + */ + public static final String HDMI_SYSTEM_AUDIO_OUTPUT = "hdmi_system_audio_output"; + + /** + * Whether TV will automatically turn on upon reception of the CEC command + * <Text View On> or <Image View On>. (0 = false, 1 = true) + * @hide + */ + public static final String HDMI_CONTROL_AUTO_WAKEUP_ENABLED = + "hdmi_control_auto_wakeup_enabled"; + + /** + * Whether TV will also turn off other CEC devices when it goes to standby mode. + * (0 = false, 1 = true) + * @hide + */ + public static final String HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED = + "hdmi_control_auto_device_off_enabled"; + + /** + * Whether TV will switch to MHL port when a mobile device is plugged in. + * (0 = false, 1 = true) + * @hide + */ + public static final String MHL_INPUT_SWITCHING_ENABLED = "mhl_input_switching_enabled"; + + /** + * Whether TV will charge the mobile device connected at MHL port. (0 = false, 1 = true) + * @hide + */ + public static final String MHL_POWER_CHARGE_ENABLED = "mhl_power_charge_enabled"; + + /** * Whether mobile data connections are allowed by the user. See * ConnectivityManager for more info. * @hide diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 8bd0f4ded551..7d5ff33dcffa 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -29,7 +29,6 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; -import android.util.ArrayMap; import android.util.Log; import java.util.List; @@ -410,28 +409,20 @@ public abstract class NotificationListenerService extends Service { } /** - * Provides access to ranking information on a currently active - * notification. + * Stores ranking related information on a currently active notification. * * <p> - * Note that this object is not updated on notification events (such as - * {@link #onNotificationPosted(StatusBarNotification, RankingMap)}, - * {@link #onNotificationRemoved(StatusBarNotification)}, etc.). Make sure - * to retrieve a new Ranking from the current {@link RankingMap} whenever - * a notification event occurs. + * Ranking objects aren't automatically updated as notification events + * occur. Instead, ranking information has to be retrieved again via the + * current {@link RankingMap}. */ public static class Ranking { - private final String mKey; - private final int mRank; - private final boolean mIsAmbient; - private final boolean mIsInterceptedByDnd; + private String mKey; + private int mRank = -1; + private boolean mIsAmbient; + private boolean mMeetsInterruptionFilter; - private Ranking(String key, int rank, boolean isAmbient, boolean isInterceptedByDnd) { - mKey = key; - mRank = rank; - mIsAmbient = isAmbient; - mIsInterceptedByDnd = isInterceptedByDnd; - } + public Ranking() {} /** * Returns the key of the notification this Ranking applies to. @@ -459,11 +450,19 @@ public abstract class NotificationListenerService extends Service { } /** - * Returns whether the notification was intercepted by - * "Do not disturb". + * Returns whether the notification meets the user's interruption + * filter. */ - public boolean isInterceptedByDoNotDisturb() { - return mIsInterceptedByDnd; + public boolean meetsInterruptionFilter() { + return mMeetsInterruptionFilter; + } + + private void populate(String key, int rank, boolean isAmbient, + boolean meetsInterruptionFilter) { + mKey = key; + mRank = rank; + mIsAmbient = isAmbient; + mMeetsInterruptionFilter = meetsInterruptionFilter; } } @@ -477,12 +476,9 @@ public abstract class NotificationListenerService extends Service { */ public static class RankingMap implements Parcelable { private final NotificationRankingUpdate mRankingUpdate; - private final ArrayMap<String, Ranking> mRankingCache; - private boolean mRankingCacheInitialized; private RankingMap(NotificationRankingUpdate rankingUpdate) { mRankingUpdate = rankingUpdate; - mRankingCache = new ArrayMap<>(rankingUpdate.getOrderedKeys().length); } /** @@ -496,37 +492,42 @@ public abstract class NotificationListenerService extends Service { } /** - * Returns the Ranking for the notification with the given key. + * Populates outRanking with ranking information for the notification + * with the given key. * - * @return the Ranking of the notification with the given key; - * <code>null</code> when the key is unknown. + * @return true if a valid key has been passed and outRanking has + * been populated; false otherwise */ - public Ranking getRanking(String key) { - synchronized (mRankingCache) { - if (!mRankingCacheInitialized) { - initializeRankingCache(); - mRankingCacheInitialized = true; - } - } - return mRankingCache.get(key); + public boolean getRanking(String key, Ranking outRanking) { + int rank = getRank(key); + outRanking.populate(key, rank, isAmbient(key), !isIntercepted(key)); + return rank >= 0; } - private void initializeRankingCache() { + private int getRank(String key) { + // TODO: Optimize. String[] orderedKeys = mRankingUpdate.getOrderedKeys(); - int firstAmbientIndex = mRankingUpdate.getFirstAmbientIndex(); for (int i = 0; i < orderedKeys.length; i++) { - String key = orderedKeys[i]; - boolean isAmbient = firstAmbientIndex > -1 && firstAmbientIndex <= i; - boolean isInterceptedByDnd = false; - // TODO: Optimize. - for (String s : mRankingUpdate.getDndInterceptedKeys()) { - if (s.equals(key)) { - isInterceptedByDnd = true; - break; - } + if (orderedKeys[i].equals(key)) { + return i; } - mRankingCache.put(key, new Ranking(key, i, isAmbient, isInterceptedByDnd)); } + return -1; + } + + private boolean isAmbient(String key) { + int rank = getRank(key); + return rank >= 0 && rank >= mRankingUpdate.getFirstAmbientIndex(); + } + + private boolean isIntercepted(String key) { + // TODO: Optimize. + for (String interceptedKey : mRankingUpdate.getInterceptedKeys()) { + if (interceptedKey.equals(key)) { + return true; + } + } + return false; } // ----------- Parcelable diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java index 4b13d9547e73..26af38b21d1c 100644 --- a/core/java/android/service/notification/NotificationRankingUpdate.java +++ b/core/java/android/service/notification/NotificationRankingUpdate.java @@ -24,20 +24,20 @@ import android.os.Parcelable; public class NotificationRankingUpdate implements Parcelable { // TODO: Support incremental updates. private final String[] mKeys; - private final String[] mDndInterceptedKeys; + private final String[] mInterceptedKeys; private final int mFirstAmbientIndex; - public NotificationRankingUpdate(String[] keys, String[] dndInterceptedKeys, + public NotificationRankingUpdate(String[] keys, String[] interceptedKeys, int firstAmbientIndex) { mKeys = keys; mFirstAmbientIndex = firstAmbientIndex; - mDndInterceptedKeys = dndInterceptedKeys; + mInterceptedKeys = interceptedKeys; } public NotificationRankingUpdate(Parcel in) { mKeys = in.readStringArray(); mFirstAmbientIndex = in.readInt(); - mDndInterceptedKeys = in.readStringArray(); + mInterceptedKeys = in.readStringArray(); } @Override @@ -49,7 +49,7 @@ public class NotificationRankingUpdate implements Parcelable { public void writeToParcel(Parcel out, int flags) { out.writeStringArray(mKeys); out.writeInt(mFirstAmbientIndex); - out.writeStringArray(mDndInterceptedKeys); + out.writeStringArray(mInterceptedKeys); } public static final Parcelable.Creator<NotificationRankingUpdate> CREATOR @@ -71,7 +71,7 @@ public class NotificationRankingUpdate implements Parcelable { return mFirstAmbientIndex; } - public String[] getDndInterceptedKeys() { - return mDndInterceptedKeys; + public String[] getInterceptedKeys() { + return mInterceptedKeys; } } diff --git a/core/java/android/speech/tts/ITextToSpeechCallback.aidl b/core/java/android/speech/tts/ITextToSpeechCallback.aidl index 96f7e0eb05fc..899515fbff5d 100644 --- a/core/java/android/speech/tts/ITextToSpeechCallback.aidl +++ b/core/java/android/speech/tts/ITextToSpeechCallback.aidl @@ -43,13 +43,6 @@ oneway interface ITextToSpeechCallback { void onStop(String utteranceId); /** - * Tells the client that the synthesis failed, and fallback synthesis will be attempted. - * - * @param utteranceId Unique id identifying synthesis request. - */ - void onFallback(String utteranceId); - - /** * Tells the client that the synthesis has failed. * * @param utteranceId Unique id identifying synthesis request. diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java index ac9044a213dd..46077ed15b34 100644 --- a/core/java/android/speech/tts/TextToSpeech.java +++ b/core/java/android/speech/tts/TextToSpeech.java @@ -1945,15 +1945,13 @@ public class TextToSpeech { private final ITextToSpeechCallback.Stub mCallback = new ITextToSpeechCallback.Stub() { public void onStop(String utteranceId) throws RemoteException { - // do nothing + UtteranceProgressListener listener = mUtteranceProgressListener; + if (listener != null) { + listener.onDone(utteranceId); + } }; @Override - public void onFallback(String utteranceId) throws RemoteException { - // do nothing - } - - @Override public void onSuccess(String utteranceId) { UtteranceProgressListener listener = mUtteranceProgressListener; if (listener != null) { diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java index ecfb8e079dc5..a0743f79b6d5 100644 --- a/core/java/android/speech/tts/TextToSpeechService.java +++ b/core/java/android/speech/tts/TextToSpeechService.java @@ -592,14 +592,12 @@ public abstract class TextToSpeechService extends Service { } interface UtteranceProgressDispatcher { - public void dispatchOnFallback(); public void dispatchOnStop(); public void dispatchOnSuccess(); public void dispatchOnStart(); public void dispatchOnError(int errorCode); } - /** Set of parameters affecting audio output. */ static class AudioOutputParams { /** @@ -770,14 +768,6 @@ public abstract class TextToSpeechService extends Service { } @Override - public void dispatchOnFallback() { - final String utteranceId = getUtteranceId(); - if (utteranceId != null) { - mCallbacks.dispatchOnFallback(getCallerIdentity(), utteranceId); - } - } - - @Override public void dispatchOnStart() { final String utteranceId = getUtteranceId(); if (utteranceId != null) { @@ -1336,16 +1326,6 @@ public abstract class TextToSpeechService extends Service { } } - public void dispatchOnFallback(Object callerIdentity, String utteranceId) { - ITextToSpeechCallback cb = getCallbackFor(callerIdentity); - if (cb == null) return; - try { - cb.onFallback(utteranceId); - } catch (RemoteException e) { - Log.e(TAG, "Callback onFallback failed: " + e); - } - } - public void dispatchOnStop(Object callerIdentity, String utteranceId) { ITextToSpeechCallback cb = getCallbackFor(callerIdentity); if (cb == null) return; diff --git a/core/java/android/text/style/TtsSpan.java b/core/java/android/text/style/TtsSpan.java index ab736c5bb0b8..04b2da501e9b 100644 --- a/core/java/android/text/style/TtsSpan.java +++ b/core/java/android/text/style/TtsSpan.java @@ -22,7 +22,7 @@ import android.text.ParcelableSpan; import android.text.TextUtils; /** - * A span that supplies addition meta-data intended for text-to-speech rendering + * A span that supplies additional meta-data intended for text-to-speech rendering * of the associated text. If the text is being processed by a text-to-speech * engine, the engine may use the data in this span in addition to or instead of * its associated text. @@ -35,37 +35,132 @@ public class TtsSpan implements ParcelableSpan { * This span type can be used to add morphosyntactic features to the text it * spans over, or synthesize a something else than the spanned text. Use * the argument {@link #ARG_TEXT} to set a different text. - * Accepts the arguments {@link TtsSpan#ARG_GENDER}, - * {@link TtsSpan#ARG_ANIMACY}, {@link TtsSpan#ARG_MULTIPLICITY} and - * {@link TtsSpan#ARG_CASE}. + * Accepts the arguments {@link #ARG_GENDER}, + * {@link #ARG_ANIMACY}, {@link #ARG_MULTIPLICITY} and + * {@link #ARG_CASE}. */ public static final String TYPE_TEXT = "android.type.text"; /** * The text associated with this span is a cardinal. Must include the * number to be synthesized with {@link #ARG_NUMBER}. - * Also accepts the arguments {@link TtsSpan#ARG_GENDER}, - * {@link TtsSpan#ARG_ANIMACY}, {@link TtsSpan#ARG_MULTIPLICITY} and - * {@link TtsSpan#ARG_CASE}. + * Also accepts the arguments {@link #ARG_GENDER}, + * {@link #ARG_ANIMACY}, {@link #ARG_MULTIPLICITY} and + * {@link #ARG_CASE}. */ public static final String TYPE_CARDINAL = "android.type.cardinal"; /** - * String supplying the text to be synthesized. The synthesizer is free - * to decide how to interpret the text. + * The text associated with this span is an ordinal. Must include the + * number to be synthesized with {@link #ARG_NUMBER}. + * Also accepts the arguments {@link #ARG_GENDER}, + * {@link #ARG_ANIMACY}, {@link #ARG_MULTIPLICITY} and + * {@link #ARG_CASE}. */ - public static final String ARG_TEXT = "android.arg.text"; + public static final String TYPE_ORDINAL = "android.type.ordinal"; /** - * Argument used to specify a whole number. The value can be a string of - * digits of any size optionally prefixed with a - or +. + * The text associated with this span is a decimal number. Must include the + * number to be synthesized with {@link #ARG_INTEGER_PART} and + * {@link #ARG_FRACTIONAL_PART}. + * Also accepts the arguments {@link #ARG_GENDER}, + * {@link #ARG_ANIMACY}, {@link #ARG_MULTIPLICITY} and + * {@link #ARG_CASE}. */ - public static final String ARG_NUMBER = "android.arg.number"; + public static final String TYPE_DECIMAL = "android.type.decimal"; + + /** + * The text associated with this span is a fractional number. Must include + * the number to be synthesized with {@link #ARG_NUMERATOR} and + * {@link #ARG_DENOMINATOR}. {@link #ARG_INTEGER_PART} is optional + * Also accepts the arguments {@link #ARG_GENDER}, + * {@link #ARG_ANIMACY}, {@link #ARG_MULTIPLICITY} and + * {@link #ARG_CASE}. + */ + public static final String TYPE_FRACTION = "android.type.fraction"; + + /** + * The text associated with this span is a measure, consisting of a number + * and a unit. The number can be a cardinal, decimal or a fraction. Set the + * number with the same arguments as {@link #TYPE_CARDINAL}, + * {@link #TYPE_DECIMAL} or {@link #TYPE_FRACTION}. The unit can be + * specified with {@link #ARG_UNIT}. + * Also accepts the arguments {@link #ARG_GENDER}, + * {@link #ARG_ANIMACY}, {@link #ARG_MULTIPLICITY} and + * {@link #ARG_CASE}. + */ + public static final String TYPE_MEASURE = "android.type.measure"; + + /** + * The text associated with this span is a time, consisting of a number of + * hours and minutes, specified with {@link #ARG_HOURS} and + * {@link #ARG_MINUTES}. + * Also accepts the arguments {@link #ARG_GENDER}, + * {@link #ARG_ANIMACY}, {@link #ARG_MULTIPLICITY} and + * {@link #ARG_CASE}. + */ + public static final String TYPE_TIME = "android.type.time"; + + /** + * The text associated with this span is a date. All arguments are optional, + * but at least one has to be provided: {@link #ARG_WEEKDAY}, + * {@link #ARG_DAY}, {@link #ARG_MONTH} and {@link #ARG_YEAR}. + * Also accepts the arguments {@link #ARG_GENDER}, + * {@link #ARG_ANIMACY}, {@link #ARG_MULTIPLICITY} and + * {@link #ARG_CASE}. + */ + public static final String TYPE_DATE = "android.type.date"; + + /** + * The text associated with this span is a telephone number. The argument + * {@link #ARG_NUMBER_PART} is required. {@link #ARG_COUNTRY_CODE} and + * {@link #ARG_EXTENSION} are optional. + * Also accepts the arguments {@link #ARG_GENDER}, + * {@link #ARG_ANIMACY}, {@link #ARG_MULTIPLICITY} and + * {@link #ARG_CASE}. + */ + public static final String TYPE_TELEPHONE = "android.type.telephone"; + + + /** + * The text associated with this span is a URI (can be used for URLs and + * email addresses). The full schema for URLs, which email addresses can + * effectively be seen as a subset of, is: + * protocol://username:password@domain:port/path?query_string#fragment_id + * Hence populating just username and domain will read as an email address. + * All arguments are optional, but at least one has to be provided: + * {@link #ARG_PROTOCOL}, {@link #ARG_USERNAME}, {@link #ARG_PASSWORD}, + * {@link #ARG_DOMAIN}, {@link #ARG_PORT}, {@link #ARG_PATH}, + * {@link #ARG_QUERY_STRING} and {@link #ARG_FRAGMENT_ID}. + * Also accepts the arguments {@link #ARG_GENDER}, + * {@link #ARG_ANIMACY}, {@link #ARG_MULTIPLICITY} and + * {@link #ARG_CASE}. + */ + public static final String TYPE_ELECTRONIC = "android.type.electronic"; + + /** + * The text associated with this span is a series of digits that have to be + * read sequentially. {@link #ARG_DIGITS} is required. + * Also accepts the arguments {@link #ARG_GENDER}, + * {@link #ARG_ANIMACY}, {@link #ARG_MULTIPLICITY} and + * {@link #ARG_CASE}. + */ + public static final String TYPE_DIGITS = "android.type.digits"; + + /** + * The text associated with this span is a series of characters that have to + * be read verbatim. The engine will attempt to ready out any character like + * punctuation but excluding whitespace. {@link #ARG_VERBATIM} is required. + * Also accepts the arguments {@link #ARG_GENDER}, + * {@link #ARG_ANIMACY}, {@link #ARG_MULTIPLICITY} and + * {@link #ARG_CASE}. + */ + public static final String TYPE_VERBATIM = "android.type.verbatim"; /** * String argument supplying gender information. Can be any of - * {@link TtsSpan#GENDER_NEUTRAL}, {@link TtsSpan#GENDER_MALE} and - * {@link TtsSpan#GENDER_FEMALE}. + * {@link #GENDER_NEUTRAL}, {@link #GENDER_MALE} and + * {@link #GENDER_FEMALE}. */ public static final String ARG_GENDER = "android.arg.gender"; @@ -75,8 +170,8 @@ public class TtsSpan implements ParcelableSpan { /** * String argument supplying animacy information. Can be - * {@link TtsSpan#ANIMACY_ANIMATE} or - * {@link TtsSpan#ANIMACY_INANIMATE} + * {@link #ANIMACY_ANIMATE} or + * {@link #ANIMACY_INANIMATE} */ public static final String ARG_ANIMACY = "android.arg.animacy"; @@ -85,9 +180,9 @@ public class TtsSpan implements ParcelableSpan { /** * String argument supplying multiplicity information. Can be any of - * {@link TtsSpan#MULTIPLICITY_SINGLE}, - * {@link TtsSpan#MULTIPLICITY_DUAL} and - * {@link TtsSpan#MULTIPLICITY_PLURAL} + * {@link #MULTIPLICITY_SINGLE}, + * {@link #MULTIPLICITY_DUAL} and + * {@link #MULTIPLICITY_PLURAL} */ public static final String ARG_MULTIPLICITY = "android.arg.multiplicity"; @@ -97,11 +192,11 @@ public class TtsSpan implements ParcelableSpan { /** * String argument supplying case information. Can be any of - * {@link TtsSpan#CASE_NOMINATIVE}, {@link TtsSpan#CASE_ACCUSATIVE}, - * {@link TtsSpan#CASE_DATIVE}, {@link TtsSpan#CASE_ABLATIVE}, - * {@link TtsSpan#CASE_GENITIVE}, {@link TtsSpan#CASE_VOCATIVE}, - * {@link TtsSpan#CASE_LOCATIVE} and - * {@link TtsSpan#CASE_INSTRUMENTAL} + * {@link #CASE_NOMINATIVE}, {@link #CASE_ACCUSATIVE}, + * {@link #CASE_DATIVE}, {@link #CASE_ABLATIVE}, + * {@link #CASE_GENITIVE}, {@link #CASE_VOCATIVE}, + * {@link #CASE_LOCATIVE} and + * {@link #CASE_INSTRUMENTAL} */ public static final String ARG_CASE = "android.arg.case"; @@ -114,6 +209,219 @@ public class TtsSpan implements ParcelableSpan { public static final String CASE_LOCATIVE = "android.locative"; public static final String CASE_INSTRUMENTAL = "android.instrumental"; + /** + * String supplying the text to be synthesized. The synthesizer is free + * to decide how to interpret the text. + * Can be used with {@link #TYPE_TEXT}. + */ + public static final String ARG_TEXT = "android.arg.text"; + + /** + * Argument used to specify a whole number. The value can be a string of + * digits of any size optionally prefixed with a - or +. + * Can be used with {@link #TYPE_CARDINAL} and {@link #TYPE_ORDINAL}. + */ + public static final String ARG_NUMBER = "android.arg.number"; + + /** + * Argument used to specify the integer part of a decimal or fraction. The + * value can be a string of digits of any size optionally prefixed with a - or +. + * Can be used with {@link #TYPE_DECIMAL} and {@link #TYPE_FRACTION}. + */ + public static final String ARG_INTEGER_PART = "android.arg.integer_part"; + + /** + * Argument used to specify the fractional part of a decimal. The value can + * be a string of digits of any size. + * Can be used with {@link #TYPE_DECIMAL}. + */ + public static final String ARG_FRACTIONAL_PART = + "android.arg.fractional_part"; + + /** + * Argument used to specify the numerator of a fraction. The value can be a + * string of digits of any size optionally prefixed with a - or +. + * Can be used with {@link #TYPE_FRACTION}. + */ + public static final String ARG_NUMERATOR = "android.arg.numerator"; + + /** + * Argument used to specify the denominator of a fraction. The value can be + * a string of digits of any size optionally prefixed with a + or -. + * Can be used with {@link #TYPE_FRACTION}. + */ + public static final String ARG_DENOMINATOR = "android.arg.denominator"; + + /** + * Argument used to specify the unit of a measure. The unit should always be + * specified in English singular form. Prefixes may be used. Engines will do + * their best to pronounce them correctly in the language used. Engines are + * expected to at least support the most common ones like 'meter', 'second', + * 'degree celcius' and 'degree fahrenheit' with some common prefixes like + * 'milli' and 'kilo'. + * Can be used with {@link #TYPE_MEASURE}. + */ + public static final String ARG_UNIT = "android.arg.unit"; + + /** + * Argument used to specify the hours of a time. The hours should be + * provided as an integer in the range from 0 up to and including 24. + * Can be used with {@link #TYPE_TIME}. + */ + public static final String ARG_HOURS = "android.arg.hours"; + + /** + * Argument used to specify the minutes of a time. The hours should be + * provided as an integer in the range from 0 up to and including 59. + * Can be used with {@link #TYPE_TIME}. + */ + public static final String ARG_MINUTES = "android.arg.minutes"; + + /** + * Argument used to specify the weekday of a date. The value should be + * provided as an integer and can be any of {@link #WEEKDAY_SUNDAY}, + * {@link #WEEKDAY_MONDAY}, {@link #WEEKDAY_TUESDAY}, + * {@link #WEEKDAY_WEDNESDAY}, {@link #WEEKDAY_THURSDAY}, + * {@link #WEEKDAY_FRIDAY} and {@link #WEEKDAY_SATURDAY}. + * Can be used with {@link #TYPE_DATE}. + */ + public static final String ARG_WEEKDAY = "android.arg.weekday"; + + public static final int WEEKDAY_SUNDAY = 1; + public static final int WEEKDAY_MONDAY = 2; + public static final int WEEKDAY_TUESDAY = 3; + public static final int WEEKDAY_WEDNESDAY = 4; + public static final int WEEKDAY_THURSDAY = 5; + public static final int WEEKDAY_FRIDAY = 6; + public static final int WEEKDAY_SATURDAY = 7; + + /** + * Argument used to specify the day of the month of a date. The value should + * be provided as an integer in the range from 1 up to and including 31. + * Can be used with {@link #TYPE_DATE}. + */ + public static final String ARG_DAY = "android.arg.day"; + + /** + * Argument used to specify the month of a date. The value should be + * provided as an integer and can be any of {@link #MONTH_JANUARY}, + * {@link #MONTH_FEBRUARY}, {@link #MONTH_MARCH}, {@link #MONTH_APRIL}, {@link #MONTH_MAY}, + * {@link #MONTH_JUNE}, {@link #MONTH_JULY}, {@link #MONTH_AUGUST}, {@link #MONTH_SEPTEMBER}, + * {@link #MONTH_OCTOBER}, {@link #MONTH_NOVEMBER} and {@link #MONTH_DECEMBER}. + * Can be used with {@link #TYPE_DATE}. + */ + public static final String ARG_MONTH = "android.arg.month"; + + public static final int MONTH_JANUARY = 0; + public static final int MONTH_FEBRUARY = 1; + public static final int MONTH_MARCH = 2; + public static final int MONTH_APRIL = 3; + public static final int MONTH_MAY = 4; + public static final int MONTH_JUNE = 5; + public static final int MONTH_JULY = 6; + public static final int MONTH_AUGUST = 7; + public static final int MONTH_SEPTEMBER = 8; + public static final int MONTH_OCTOBER = 9; + public static final int MONTH_NOVEMBER = 10; + public static final int MONTH_DECEMBER = 11; + + /** + * Argument used to specify the year of a date. The value should be provided + * as a positive integer. + * Can be used with {@link #TYPE_DATE}. + */ + public static final String ARG_YEAR = "android.arg.year"; + + /** + * Argument used to specify the country code of a telephone number. Can be + * a string of digits. + * Can be used with {@link #TYPE_TELEPHONE}. + */ + public static final String ARG_COUNTRY_CODE = "android.arg.country_code"; + + /** + * Argument used to specify the main number part of a telephone number. Can + * be a string of digits. + * Can be used with {@link #TYPE_TELEPHONE}. + */ + public static final String ARG_NUMBER_PART = "android.arg.number_part"; + + /** + * Argument used to specify the extension part of a telephone number. Can be + * a string of digits. + * Can be used with {@link #TYPE_TELEPHONE}. + */ + public static final String ARG_EXTENSION = "android.arg.extension"; + + /** + * Argument used to specify the protocol of a URI. Examples are 'http' and + * 'ftp'. + * Can be used with {@link #TYPE_ELECTRONIC}. + */ + public static final String ARG_PROTOCOL = "android.arg.protocol"; + + /** + * Argument used to specify the username part of a URI. Should be set as a + * string. + * Can be used with {@link #TYPE_ELECTRONIC}. + */ + public static final String ARG_USERNAME = "android.arg.username"; + + /** + * Argument used to specify the password part of a URI. Should be set as a + * string. + * Can be used with {@link #TYPE_ELECTRONIC}. + */ + public static final String ARG_PASSWORD = "android.arg.password"; + + /** + * Argument used to specify the domain part of a URI. For example are + * 'source.android.com'. + * Can be used with {@link #TYPE_ELECTRONIC}. + */ + public static final String ARG_DOMAIN = "android.arg.domain"; + + /** + * Argument used to specify the port number of a URI. Should be specified as + * an integer. + * Can be used with {@link #TYPE_ELECTRONIC}. + */ + public static final String ARG_PORT = "android.arg.port"; + + /** + * Argument used to specify the path part of a URI. For example + * 'source/index.html'. + * Can be used with {@link #TYPE_ELECTRONIC}. + */ + public static final String ARG_PATH = "android.arg.path"; + + /** + * Argument used to specify the query string of a URI. For example + * 'arg=value&argtwo=value'. + * Can be used with {@link #TYPE_ELECTRONIC}. + */ + public static final String ARG_QUERY_STRING = "android.arg.query_string"; + + /** + * Argument used to specify the fragment id of a URI. Should be specified as + * a string. + * Can be used with {@link #TYPE_ELECTRONIC}. + */ + public static final String ARG_FRAGMENT_ID = "android.arg.fragment_id"; + + /** + * Argument used to specify a string of digits. + * Can be used with {@link #TYPE_DIGITS}. + */ + public static final String ARG_DIGITS = "android.arg.digits"; + + /** + * Argument used to specify a string where the characters are read verbatim, + * except whitespace. + * Can be used with {@link #TYPE_VERBATIM}. + */ + public static final String ARG_VERBATIM = "android.arg.verbatim"; + public TtsSpan(String type, PersistableBundle args) { mType = type; mArgs = args; diff --git a/core/java/android/transition/ChangeBounds.java b/core/java/android/transition/ChangeBounds.java index 2591e24ecd55..3f5e8e8fbc20 100644 --- a/core/java/android/transition/ChangeBounds.java +++ b/core/java/android/transition/ChangeBounds.java @@ -310,7 +310,8 @@ public class ChangeBounds extends Transition { Canvas canvas = new Canvas(bitmap); view.draw(canvas); final BitmapDrawable drawable = new BitmapDrawable(bitmap); - view.setVisibility(View.INVISIBLE); + final float transitionAlpha = view.getTransitionAlpha(); + view.setTransitionAlpha(0); sceneRoot.getOverlay().add(drawable); Path topLeftPath = getPathMotion().getPath(startX - tempLocation[0], startY - tempLocation[1], endX - tempLocation[0], endY - tempLocation[1]); @@ -321,7 +322,7 @@ public class ChangeBounds extends Transition { @Override public void onAnimationEnd(Animator animation) { sceneRoot.getOverlay().remove(drawable); - view.setVisibility(View.VISIBLE); + view.setTransitionAlpha(transitionAlpha); } }); return anim; diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java index c14678aea92b..19672138b512 100644 --- a/core/java/android/transition/Transition.java +++ b/core/java/android/transition/Transition.java @@ -2037,6 +2037,9 @@ public abstract class Transition implements Cloneable { return mNameOverrides; } + /** @hide */ + public void forceVisibility(int visibility, boolean isStartValue) {} + @Override public String toString() { return toString(""); diff --git a/core/java/android/transition/TransitionSet.java b/core/java/android/transition/TransitionSet.java index 63957e9bb0cb..c6005b9a6deb 100644 --- a/core/java/android/transition/TransitionSet.java +++ b/core/java/android/transition/TransitionSet.java @@ -293,6 +293,15 @@ public class TransitionSet extends Transition { } } + /** @hide */ + @Override + public void forceVisibility(int visibility, boolean isStartValue) { + int numTransitions = mTransitions.size(); + for (int i = 0; i < numTransitions; i++) { + mTransitions.get(i).forceVisibility(visibility, isStartValue); + } + } + /** * Removes the specified child transition from this set. * diff --git a/core/java/android/transition/Visibility.java b/core/java/android/transition/Visibility.java index 1629726cadd5..cf5ea4c6dfb5 100644 --- a/core/java/android/transition/Visibility.java +++ b/core/java/android/transition/Visibility.java @@ -78,6 +78,9 @@ public abstract class Visibility extends Transition { private int mMode = IN | OUT; + private int mForcedStartVisibility = -1; + private int mForcedEndVisibility = -1; + public Visibility() {} public Visibility(Context context, AttributeSet attrs) { @@ -121,8 +124,13 @@ public abstract class Visibility extends Transition { return sTransitionProperties; } - private void captureValues(TransitionValues transitionValues) { - int visibility = transitionValues.view.getVisibility(); + private void captureValues(TransitionValues transitionValues, int forcedVisibility) { + int visibility; + if (forcedVisibility != -1) { + visibility = forcedVisibility; + } else { + visibility = transitionValues.view.getVisibility(); + } transitionValues.values.put(PROPNAME_VISIBILITY, visibility); transitionValues.values.put(PROPNAME_PARENT, transitionValues.view.getParent()); int[] loc = new int[2]; @@ -132,12 +140,22 @@ public abstract class Visibility extends Transition { @Override public void captureStartValues(TransitionValues transitionValues) { - captureValues(transitionValues); + captureValues(transitionValues, mForcedStartVisibility); } @Override public void captureEndValues(TransitionValues transitionValues) { - captureValues(transitionValues); + captureValues(transitionValues, mForcedEndVisibility); + } + + /** @hide */ + @Override + public void forceVisibility(int visibility, boolean isStartValue) { + if (isStartValue) { + mForcedStartVisibility = visibility; + } else { + mForcedEndVisibility = visibility; + } } /** @@ -402,26 +420,29 @@ public abstract class Visibility extends Transition { } if (viewToKeep != null) { - int originalVisibility = viewToKeep.getVisibility(); - viewToKeep.setVisibility(View.VISIBLE); + int originalVisibility = -1; + final boolean isForcedVisibility = mForcedStartVisibility != -1 || + mForcedEndVisibility != -1; + if (!isForcedVisibility) { + originalVisibility = viewToKeep.getVisibility(); + viewToKeep.setVisibility(View.VISIBLE); + } Animator animator = onDisappear(sceneRoot, viewToKeep, startValues, endValues); - if (animator == null) { - viewToKeep.setVisibility(originalVisibility); - } else { + if (animator != null) { final View finalViewToKeep = viewToKeep; animator.addListener(new AnimatorListenerAdapter() { boolean mCanceled = false; @Override public void onAnimationPause(Animator animation) { - if (!mCanceled) { + if (!mCanceled && !isForcedVisibility) { finalViewToKeep.setVisibility(finalVisibility); } } @Override public void onAnimationResume(Animator animation) { - if (!mCanceled) { + if (!mCanceled && !isForcedVisibility) { finalViewToKeep.setVisibility(View.VISIBLE); } } @@ -434,10 +455,16 @@ public abstract class Visibility extends Transition { @Override public void onAnimationEnd(Animator animation) { if (!mCanceled) { - finalViewToKeep.setVisibility(finalVisibility); + if (isForcedVisibility) { + finalViewToKeep.setTransitionAlpha(0); + } else { + finalViewToKeep.setVisibility(finalVisibility); + } } } }); + } else if (!isForcedVisibility) { + viewToKeep.setVisibility(originalVisibility); } return animator; } diff --git a/core/java/android/view/HardwareCanvas.java b/core/java/android/view/HardwareCanvas.java index 263ebdaf1667..c17296920599 100644 --- a/core/java/android/view/HardwareCanvas.java +++ b/core/java/android/view/HardwareCanvas.java @@ -112,4 +112,8 @@ public abstract class HardwareCanvas extends Canvas { public abstract void drawCircle(CanvasProperty<Float> cx, CanvasProperty<Float> cy, CanvasProperty<Float> radius, CanvasProperty<Paint> paint); + + public static void setProperty(String name, String value) { + GLES20Canvas.setProperty(name, value); + } } diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index d69d01d661ba..be677ea60214 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -18,6 +18,7 @@ package android.view; import android.content.Context; import android.graphics.Bitmap; +import android.graphics.Rect; import android.util.DisplayMetrics; import android.view.Surface.OutOfResourcesException; @@ -247,21 +248,24 @@ public abstract class HardwareRenderer { abstract void detachSurfaceTexture(long hardwareLayer); /** - * Setup the hardware renderer for drawing. This is called whenever the - * size of the target surface changes or when the surface is first created. + * Setup the hardware renderer for drawing. This is called whenever the size + * of the target surface changes or when the surface is first created. * * @param width Width of the drawing surface. * @param height Height of the drawing surface. + * @param surfaceInsets Insets between the drawing surface and actual + * surface bounds. * @param lightX X position of the shadow casting light * @param lightY Y position of the shadow casting light * @param lightZ Z position of the shadow casting light * @param lightRadius radius of the shadow casting light */ - abstract void setup(int width, int height, float lightX, float lightY, float lightZ, float lightRadius); + abstract void setup(int width, int height, Rect surfaceInsets, float lightX, float lightY, + float lightZ, float lightRadius); /** * Gets the current width of the surface. This is the width that the surface - * was last set to in a call to {@link #setup(int, int, float, float, float, float)}. + * was last set to in a call to {@link #setup(int, int, Rect, float, float, float, float)}. * * @return the current width of the surface */ @@ -269,7 +273,7 @@ public abstract class HardwareRenderer { /** * Gets the current height of the surface. This is the height that the surface - * was last set to in a call to {@link #setup(int, int, float, float, float, float)}. + * was last set to in a call to {@link #setup(int, int, Rect, float, float, float, float)}. * * @return the current width of the surface */ @@ -344,7 +348,6 @@ public abstract class HardwareRenderer { * @param view The view to draw. * @param attachInfo AttachInfo tied to the specified view. * @param callbacks Callbacks invoked when drawing happens. - * @param dirty The dirty rectangle to update, can be null. */ abstract void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks); @@ -369,17 +372,18 @@ public abstract class HardwareRenderer { * @param height The height of the drawing surface. * @param surface The surface to hardware accelerate * @param metrics The display metrics used to draw the output. + * @param surfaceInsets The drawing surface insets to apply * * @return true if the surface was initialized, false otherwise. Returning * false might mean that the surface was already initialized. */ - boolean initializeIfNeeded(int width, int height, Surface surface, DisplayMetrics metrics) + boolean initializeIfNeeded(int width, int height, Surface surface, Rect surfaceInsets, DisplayMetrics metrics) throws OutOfResourcesException { if (isRequested()) { // We lost the gl context, so recreate it. if (!isEnabled()) { if (initialize(surface)) { - setup(width, height, metrics); + setup(width, height, surfaceInsets, metrics); return true; } } @@ -387,12 +391,12 @@ public abstract class HardwareRenderer { return false; } - void setup(int width, int height, DisplayMetrics metrics) { + void setup(int width, int height, Rect surfaceInsets, DisplayMetrics metrics) { float lightX = width / 2.0f; float lightY = -400 * metrics.density; float lightZ = 800 * metrics.density; float lightRadius = 800 * metrics.density; - setup(width, height, lightX, lightY, lightZ, lightRadius); + setup(width, height, surfaceInsets, lightX, lightY, lightZ, lightRadius); } /** diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index 57d1bebe23a6..acb2fe48f9f1 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -19,6 +19,7 @@ package android.view; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; +import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.IBinder; import android.os.RemoteException; @@ -67,7 +68,16 @@ public class ThreadedRenderer extends HardwareRenderer { PROFILE_PROPERTY_VISUALIZE_BARS, }; + // Size of the rendered content. private int mWidth, mHeight; + + // Actual size of the drawing surface. + private int mSurfaceWidth, mSurfaceHeight; + + // Insets between the drawing surface and rendered content. These are + // applied as translation when updating the root render node. + private int mInsetTop, mInsetLeft; + private long mNativeProxy; private boolean mInitialized = false; private RenderNode mRootNode; @@ -154,11 +164,23 @@ public class ThreadedRenderer extends HardwareRenderer { } @Override - void setup(int width, int height, float lightX, float lightY, float lightZ, float lightRadius) { + void setup(int width, int height, Rect surfaceInsets, float lightX, float lightY, float lightZ, + float lightRadius) { mWidth = width; mHeight = height; - mRootNode.setLeftTopRightBottom(0, 0, mWidth, mHeight); - nSetup(mNativeProxy, width, height, lightX, lightY, lightZ, lightRadius); + if (surfaceInsets != null) { + mInsetLeft = surfaceInsets.left; + mInsetTop = surfaceInsets.top; + mSurfaceWidth = width + mInsetLeft + surfaceInsets.right; + mSurfaceHeight = height + mInsetTop + surfaceInsets.bottom; + } else { + mInsetLeft = 0; + mInsetTop = 0; + mSurfaceWidth = width; + mSurfaceHeight = height; + } + mRootNode.setLeftTopRightBottom(-mInsetLeft, -mInsetTop, mSurfaceWidth, mSurfaceHeight); + nSetup(mNativeProxy, mSurfaceWidth, mSurfaceHeight, lightX, lightY, lightZ, lightRadius); } @Override @@ -214,9 +236,10 @@ public class ThreadedRenderer extends HardwareRenderer { view.mPrivateFlags &= ~View.PFLAG_INVALIDATED; Trace.traceBegin(Trace.TRACE_TAG_VIEW, "getDisplayList"); - HardwareCanvas canvas = mRootNode.start(mWidth, mHeight); + HardwareCanvas canvas = mRootNode.start(mSurfaceWidth, mSurfaceHeight); try { canvas.save(); + canvas.translate(mInsetLeft, mInsetTop); callbacks.onHardwarePreDraw(canvas); canvas.drawRenderNode(view.getDisplayList()); callbacks.onHardwarePostDraw(canvas); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 5def940c8f7b..940529940e63 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1713,7 +1713,8 @@ public final class ViewRootImpl implements ViewParent, if (hwInitialized || mWidth != mAttachInfo.mHardwareRenderer.getWidth() || mHeight != mAttachInfo.mHardwareRenderer.getHeight()) { - mAttachInfo.mHardwareRenderer.setup(mWidth, mHeight, + final Rect shadowInsets = params != null ? params.shadowInsets : null; + mAttachInfo.mHardwareRenderer.setup(mWidth, mHeight, shadowInsets, mAttachInfo.mRootView.getResources().getDisplayMetrics()); if (!hwInitialized) { mAttachInfo.mHardwareRenderer.invalidate(mSurface); @@ -2211,20 +2212,22 @@ public final class ViewRootImpl implements ViewParent, return measureSpec; } + int mHardwareXOffset; int mHardwareYOffset; int mResizeAlpha; final Paint mResizePaint = new Paint(); @Override public void onHardwarePreDraw(HardwareCanvas canvas) { - canvas.translate(0, -mHardwareYOffset); + canvas.translate(-mHardwareXOffset, -mHardwareYOffset); } @Override public void onHardwarePostDraw(HardwareCanvas canvas) { if (mResizeBuffer != null) { mResizePaint.setAlpha(mResizeAlpha); - canvas.drawHardwareLayer(mResizeBuffer, 0.0f, mHardwareYOffset, mResizePaint); + canvas.drawHardwareLayer(mResizeBuffer, mHardwareXOffset, mHardwareYOffset, + mResizePaint); } drawAccessibilityFocusedDrawableIfNeeded(canvas); } @@ -2368,15 +2371,17 @@ public final class ViewRootImpl implements ViewParent, attachInfo.mTreeObserver.dispatchOnScrollChanged(); } - int yoff; + final WindowManager.LayoutParams params = mWindowAttributes; + final Rect surfaceInsets = params != null ? params.shadowInsets : null; boolean animating = mScroller != null && mScroller.computeScrollOffset(); + final int curScrollY; if (animating) { - yoff = mScroller.getCurrY(); + curScrollY = mScroller.getCurrY(); } else { - yoff = mScrollY; + curScrollY = mScrollY; } - if (mCurScrollY != yoff) { - mCurScrollY = yoff; + if (mCurScrollY != curScrollY) { + mCurScrollY = curScrollY; fullRedrawNeeded = true; } @@ -2425,11 +2430,14 @@ public final class ViewRootImpl implements ViewParent, attachInfo.mTreeObserver.dispatchOnDraw(); + final int xOffset = surfaceInsets != null ? -surfaceInsets.left : 0; + final int yOffset = curScrollY + (surfaceInsets != null ? -surfaceInsets.top : 0); if (!dirty.isEmpty() || mIsAnimating) { if (attachInfo.mHardwareRenderer != null && attachInfo.mHardwareRenderer.isEnabled()) { // Draw with hardware renderer. mIsAnimating = false; - mHardwareYOffset = yoff; + mHardwareYOffset = yOffset; + mHardwareXOffset = xOffset; mResizeAlpha = resizeAlpha; dirty.setEmpty(); @@ -2450,8 +2458,9 @@ public final class ViewRootImpl implements ViewParent, attachInfo.mHardwareRenderer.isRequested()) { try { - attachInfo.mHardwareRenderer.initializeIfNeeded(mWidth, mHeight, - mSurface, attachInfo.mRootView.getResources().getDisplayMetrics()); + attachInfo.mHardwareRenderer.initializeIfNeeded( + mWidth, mHeight, mSurface, surfaceInsets, + attachInfo.mRootView.getResources().getDisplayMetrics()); } catch (OutOfResourcesException e) { handleOutOfResourcesException(e); return; @@ -2462,7 +2471,7 @@ public final class ViewRootImpl implements ViewParent, return; } - if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) { + if (!drawSoftware(surface, attachInfo, xOffset, yOffset, scalingRequired, dirty)) { return; } } @@ -2475,9 +2484,9 @@ public final class ViewRootImpl implements ViewParent, } /** - * @return true if drawing was succesfull, false if an error occurred + * @return true if drawing was successful, false if an error occurred */ - private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int yoff, + private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty) { // Draw with software renderer. @@ -2526,7 +2535,7 @@ public final class ViewRootImpl implements ViewParent, // If we are applying an offset, we need to clear the area // where the offset doesn't appear to avoid having garbage // left in the blank areas. - if (!canvas.isOpaque() || yoff != 0) { + if (!canvas.isOpaque() || yoff != 0 || xoff != 0) { canvas.drawColor(0, PorterDuff.Mode.CLEAR); } @@ -2542,7 +2551,7 @@ public final class ViewRootImpl implements ViewParent, ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo()); } try { - canvas.translate(0, -yoff); + canvas.translate(-xoff, -yoff); if (mTranslator != null) { mTranslator.translateCanvas(canvas); } @@ -3147,8 +3156,10 @@ public final class ViewRootImpl implements ViewParent, if (mAttachInfo.mHardwareRenderer != null && mSurface.isValid()){ mFullRedrawNeeded = true; try { + final WindowManager.LayoutParams lp = mWindowAttributes; + final Rect surfaceInsets = lp != null ? lp.shadowInsets : null; mAttachInfo.mHardwareRenderer.initializeIfNeeded( - mWidth, mHeight, mSurface, + mWidth, mHeight, mSurface, surfaceInsets, mAttachInfo.mRootView.getResources().getDisplayMetrics()); } catch (OutOfResourcesException e) { Log.e(TAG, "OutOfResourcesException locking surface", e); diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 4eecc6a0b62c..c06b5d8f50a9 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -19,7 +19,9 @@ package android.view; import android.app.Presentation; import android.content.Context; import android.content.pm.ActivityInfo; +import android.graphics.Insets; import android.graphics.PixelFormat; +import android.graphics.Rect; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; @@ -1290,6 +1292,13 @@ public interface WindowManager extends ViewManager { * field is added with {@link #y} to supply the <var>yAdj</var> parameter. */ public float verticalMargin; + + /** + * Positive insets between the drawing surface and window content. + * + * @hide + */ + public Rect shadowInsets = new Rect(); /** * The desired bitmap format. May be one of the constants in @@ -1571,6 +1580,10 @@ public interface WindowManager extends ViewManager { out.writeInt(hasSystemUiListeners ? 1 : 0); out.writeInt(inputFeatures); out.writeLong(userActivityTimeout); + out.writeInt(shadowInsets.left); + out.writeInt(shadowInsets.top); + out.writeInt(shadowInsets.right); + out.writeInt(shadowInsets.bottom); } public static final Parcelable.Creator<LayoutParams> CREATOR @@ -1613,6 +1626,7 @@ public interface WindowManager extends ViewManager { hasSystemUiListeners = in.readInt() != 0; inputFeatures = in.readInt(); userActivityTimeout = in.readLong(); + shadowInsets.set(in.readInt(), in.readInt(), in.readInt(), in.readInt()); } @SuppressWarnings({"PointlessBitwiseExpression"}) @@ -1644,6 +1658,8 @@ public interface WindowManager extends ViewManager { /** {@hide} */ public static final int TRANSLUCENT_FLAGS_CHANGED = 1<<19; /** {@hide} */ + public static final int SHADOW_INSETS_CHANGED = 1<<20; + /** {@hide} */ public static final int EVERYTHING_CHANGED = 0xffffffff; // internal buffer to backup/restore parameters under compatibility mode. @@ -1778,6 +1794,11 @@ public interface WindowManager extends ViewManager { changes |= USER_ACTIVITY_TIMEOUT_CHANGED; } + if (!shadowInsets.equals(o.shadowInsets)) { + shadowInsets.set(o.shadowInsets); + changes |= SHADOW_INSETS_CHANGED; + } + return changes; } @@ -1877,6 +1898,9 @@ public interface WindowManager extends ViewManager { if (userActivityTimeout >= 0) { sb.append(" userActivityTimeout=").append(userActivityTimeout); } + if (!shadowInsets.equals(Insets.NONE)) { + sb.append(" shadowInsets=").append(shadowInsets); + } sb.append('}'); return sb.toString(); } diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index 01632ae76309..a35d4470aad4 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -22,6 +22,7 @@ import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; +import android.graphics.Insets; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.Drawable; @@ -109,6 +110,8 @@ public class PopupWindow { private int mPopupWidth; private int mPopupHeight; + private float mElevation; + private int[] mDrawingLocation = new int[2]; private int[] mScreenLocation = new int[2]; private Rect mTempRect = new Rect(); @@ -196,6 +199,7 @@ public class PopupWindow { attrs, com.android.internal.R.styleable.PopupWindow, defStyleAttr, defStyleRes); mBackground = a.getDrawable(R.styleable.PopupWindow_popupBackground); + mElevation = a.getDimension(R.styleable.PopupWindow_popupElevation, 0); mOverlapAnchor = a.getBoolean(R.styleable.PopupWindow_overlapAnchor, false); final int animStyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle, -1); @@ -319,25 +323,49 @@ public class PopupWindow { } /** - * <p>Return the drawable used as the popup window's background.</p> + * Return the drawable used as the popup window's background. * - * @return the background drawable or null + * @return the background drawable or {@code null} if not set + * @see #setBackgroundDrawable(Drawable) + * @attr ref android.R.styleable#PopupWindow_popupBackground */ public Drawable getBackground() { return mBackground; } /** - * <p>Change the background drawable for this popup window. The background - * can be set to null.</p> + * Specifies the background drawable for this popup window. The background + * can be set to {@code null}. * * @param background the popup's background + * @see #getBackground() + * @attr ref android.R.styleable#PopupWindow_popupBackground */ public void setBackgroundDrawable(Drawable background) { mBackground = background; } /** + * @return the elevation for this popup window in pixels + * @see #setElevation(float) + * @attr ref android.R.styleable#PopupWindow_popupElevation + */ + public float getElevation() { + return mElevation; + } + + /** + * Specifies the elevation for this popup window. + * + * @param elevation the popup's elevation in pixels + * @see #getElevation() + * @attr ref android.R.styleable#PopupWindow_popupElevation + */ + public void setElevation(float elevation) { + mElevation = elevation; + } + + /** * <p>Return the animation style to use the popup appears and disappears</p> * * @return the animation style to use the popup appears and disappears @@ -973,7 +1001,7 @@ public class PopupWindow { /** * <p>Prepare the popup by embedding in into a new ViewGroup if the * background drawable is not null. If embedding is required, the layout - * parameters' height is mnodified to take into account the background's + * parameters' height is modified to take into account the background's * padding.</p> * * @param p the layout parameters of the popup's content view @@ -998,13 +1026,15 @@ public class PopupWindow { PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, height ); - popupViewContainer.setBackgroundDrawable(mBackground); + popupViewContainer.setBackground(mBackground); popupViewContainer.addView(mContentView, listParams); mPopupView = popupViewContainer; } else { mPopupView = mContentView; } + + mPopupView.setElevation(mElevation); mPopupViewInitialLayoutDirectionInherited = (mPopupView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT); mPopupWidth = p.width; @@ -1066,6 +1096,10 @@ public class PopupWindow { p.softInputMode = mSoftInputMode; p.setTitle("PopupWindow:" + Integer.toHexString(hashCode())); + // TODO: Use real shadow insets once that algorithm is finalized. + final int shadowInset = (int) Math.ceil(mElevation * 2); + p.shadowInsets.set(shadowInset, shadowInset, shadowInset, shadowInset); + return p; } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index dd58ba6d4c03..fac0eb269a26 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -279,6 +279,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private ColorStateList mTextColor; private ColorStateList mHintTextColor; private ColorStateList mLinkTextColor; + @ViewDebug.ExportedProperty(category = "text") private int mCurTextColor; private int mCurHintTextColor; private boolean mFreezesText; @@ -2519,6 +2520,26 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** + * @return the size (in scaled pixels) of thee default text size in this TextView. + * @hide + */ + @ViewDebug.ExportedProperty(category = "text") + public float getScaledTextSize() { + return mTextPaint.getTextSize() / mTextPaint.density; + } + + /** @hide */ + @ViewDebug.ExportedProperty(category = "text", mapping = { + @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"), + @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"), + @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"), + @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC") + }) + public int getTypefaceStyle() { + return mTextPaint.getTypeface().getStyle(); + } + + /** * Set the default text size to the given value, interpreted as "scaled * pixel" units. This size is adjusted based on the current density and * user font size preference. diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java index d66a7bbf8f3e..d76d0d4aa251 100644 --- a/core/java/com/android/internal/content/NativeLibraryHelper.java +++ b/core/java/com/android/internal/content/NativeLibraryHelper.java @@ -131,10 +131,15 @@ public class NativeLibraryHelper { * * @return size of all native binary files in bytes */ - public static long sumNativeBinariesLI(Handle handle, String abi) { + public static long sumNativeBinariesLI(Handle handle, String[] abis) { long sum = 0; for (long apkHandle : handle.apkHandles) { - sum += nativeSumNativeBinaries(apkHandle, abi); + // NOTE: For a given APK handle, we parse the central directory precisely + // once, but prefix matching of entries requires a CD traversal, which can + // take a while (even if it needs no additional I/O). + for (String abi : abis) { + sum += nativeSumNativeBinaries(apkHandle, abi); + } } return sum; } @@ -196,45 +201,47 @@ public class NativeLibraryHelper { private native static int nativeFindSupportedAbi(long handle, String[] supportedAbis); // Convenience method to call removeNativeBinariesFromDirLI(File) - public static boolean removeNativeBinariesLI(String nativeLibraryPath) { - if (nativeLibraryPath == null) return false; - return removeNativeBinariesFromDirLI(new File(nativeLibraryPath)); + public static void removeNativeBinariesLI(String nativeLibraryPath) { + if (nativeLibraryPath == null) return; + removeNativeBinariesFromDirLI(new File(nativeLibraryPath), false /* delete root dir */); } - // Remove the native binaries of a given package. This simply - // gets rid of the files in the 'lib' sub-directory. - public static boolean removeNativeBinariesFromDirLI(File nativeLibraryDir) { + /** + * Remove the native binaries of a given package. This deletes the files + */ + public static void removeNativeBinariesFromDirLI(File nativeLibraryRoot, boolean deleteRootDir) { if (DEBUG_NATIVE) { - Slog.w(TAG, "Deleting native binaries from: " + nativeLibraryDir.getPath()); + Slog.w(TAG, "Deleting native binaries from: " + nativeLibraryRoot.getPath()); } - boolean deletedFiles = false; - /* * Just remove any file in the directory. Since the directory is owned * by the 'system' UID, the application is not supposed to have written * anything there. */ - if (nativeLibraryDir.exists()) { - final File[] binaries = nativeLibraryDir.listFiles(); - if (binaries != null) { - for (int nn = 0; nn < binaries.length; nn++) { + if (nativeLibraryRoot.exists()) { + final File[] files = nativeLibraryRoot.listFiles(); + if (files != null) { + for (int nn = 0; nn < files.length; nn++) { if (DEBUG_NATIVE) { - Slog.d(TAG, " Deleting " + binaries[nn].getName()); + Slog.d(TAG, " Deleting " + files[nn].getName()); } - if (!binaries[nn].delete()) { - Slog.w(TAG, "Could not delete native binary: " + binaries[nn].getPath()); - } else { - deletedFiles = true; + if (files[nn].isDirectory()) { + removeNativeBinariesFromDirLI(files[nn], true /* delete root dir */); + } else if (!files[nn].delete()) { + Slog.w(TAG, "Could not delete native binary: " + files[nn].getPath()); } } } - // Do not delete 'lib' directory itself, or this will prevent - // installation of future updates. + // Do not delete 'lib' directory itself, unless we're specifically + // asked to or this will prevent installation of future updates. + if (deleteRootDir) { + if (!nativeLibraryRoot.delete()) { + Slog.w(TAG, "Could not delete native binary directory: " + nativeLibraryRoot.getPath()); + } + } } - - return deletedFiles; } // We don't care about the other return values for now. diff --git a/core/jni/Android.mk b/core/jni/Android.mk index 869a91b03a52..9b9649516167 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -242,7 +242,8 @@ LOCAL_SHARED_LIBRARIES := \ libnetd_client \ libsoundtrigger \ libminikin \ - libstlport + libstlport \ + libprocessgroup \ ifeq ($(USE_OPENGL_RENDERER),true) LOCAL_SHARED_LIBRARIES += libhwui diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp index 6f898000bb0e..a75d5479e21c 100644 --- a/core/jni/android_net_NetUtils.cpp +++ b/core/jni/android_net_NetUtils.cpp @@ -285,6 +285,11 @@ static jboolean android_net_utils_bindSocketToNetwork(JNIEnv *env, jobject thiz, return (jboolean) !setNetworkForSocket(netId, socket); } +static jboolean android_net_utils_protectFromVpn(JNIEnv *env, jobject thiz, jint socket) +{ + return (jboolean) !protectFromVpn(socket); +} + // ---------------------------------------------------------------------------- /* @@ -308,6 +313,7 @@ static JNINativeMethod gNetworkUtilMethods[] = { { "bindProcessToNetworkForHostResolution", "(I)Z", (void*) android_net_utils_bindProcessToNetworkForHostResolution }, { "unbindProcessToNetworkForHostResolution", "()Z", (void*) android_net_utils_unbindProcessToNetworkForHostResolution }, { "bindSocketToNetwork", "(II)Z", (void*) android_net_utils_bindSocketToNetwork }, + { "protectFromVpn", "(I)Z", (void*)android_net_utils_protectFromVpn }, }; int register_android_net_NetworkUtils(JNIEnv* env) diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp index a6b65cc02a5d..aaa680f8cd5b 100644 --- a/core/jni/android_util_Process.cpp +++ b/core/jni/android_util_Process.cpp @@ -24,6 +24,7 @@ #include <cutils/sched_policy.h> #include <utils/String8.h> #include <utils/Vector.h> +#include <processgroup/processgroup.h> #include <android_runtime/AndroidRuntime.h> @@ -1002,6 +1003,16 @@ jintArray android_os_Process_getPidsForCommands(JNIEnv* env, jobject clazz, return pidArray; } +jint android_os_Process_killProcessGroup(JNIEnv* env, jobject clazz, jint uid, jint pid) +{ + return killProcessGroup(uid, pid, SIGKILL); +} + +void android_os_Process_removeAllProcessGroups(JNIEnv* env, jobject clazz) +{ + return removeAllProcessGroups(); +} + static const JNINativeMethod methods[] = { {"getUidForName", "(Ljava/lang/String;)I", (void*)android_os_Process_getUidForName}, {"getGidForName", "(Ljava/lang/String;)I", (void*)android_os_Process_getGidForName}, @@ -1029,6 +1040,8 @@ static const JNINativeMethod methods[] = { {"getPss", "(I)J", (void*)android_os_Process_getPss}, {"getPidsForCommands", "([Ljava/lang/String;)[I", (void*)android_os_Process_getPidsForCommands}, //{"setApplicationObject", "(Landroid/os/IBinder;)V", (void*)android_os_Process_setApplicationObject}, + {"killProcessGroup", "(II)I", (void*)android_os_Process_killProcessGroup}, + {"removeAllProcessGroups", "()V", (void*)android_os_Process_removeAllProcessGroups}, }; const char* const kProcessPathName = "android/os/Process"; diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 0cdddbae6c47..989b60eef0d2 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -41,6 +41,7 @@ #include <cutils/sched_policy.h> #include <utils/String8.h> #include <selinux/android.h> +#include <processgroup/processgroup.h> #include "android_runtime/AndroidRuntime.h" #include "JNIHelp.h" @@ -435,6 +436,14 @@ static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArra } } + if (!is_system_server) { + int rc = createProcessGroup(uid, getpid()); + if (rc != 0) { + ALOGE("createProcessGroup(%d, %d) failed: %s", uid, pid, strerror(-rc)); + RuntimeAbort(env); + } + } + SetGids(env, javaGids); SetRLimits(env, javaRlimits); diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index e38fce943b40..c34a971722cf 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -80,6 +80,7 @@ <protected-broadcast android:name="android.app.action.EXIT_CAR_MODE" /> <protected-broadcast android:name="android.app.action.ENTER_DESK_MODE" /> <protected-broadcast android:name="android.app.action.EXIT_DESK_MODE" /> + <protected-broadcast android:name="android.app.action.NEXT_ALARM_CLOCK_CHANGED" /> <protected-broadcast android:name="android.appwidget.action.APPWIDGET_UPDATE_OPTIONS" /> <protected-broadcast android:name="android.appwidget.action.APPWIDGET_DELETED" /> diff --git a/core/res/res/drawable/popup_background_material.xml b/core/res/res/drawable/popup_background_material.xml index 9e507903ea28..b1f0cf595771 100644 --- a/core/res/res/drawable/popup_background_material.xml +++ b/core/res/res/drawable/popup_background_material.xml @@ -14,7 +14,12 @@ limitations under the License. --> -<nine-patch xmlns:android="http://schemas.android.com/apk/res/android" - android:src="@drawable/popup_background_mtrl_mult" - android:tint="?attr/colorBackground" - android:tintMode="multiply" /> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + + <corners + android:radius="2dp" /> + <solid + android:color="?attr/colorBackground" /> + +</shape> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index ea9e189fa80e..53eb9d6e883f 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -4057,6 +4057,8 @@ <declare-styleable name="PopupWindow"> <!-- The background to use for the popup window. --> <attr name="popupBackground" format="reference|color" /> + <!-- Window elevation to use for the popup window. --> + <attr name="popupElevation" format="dimension" /> <!-- The animation style to use for the popup window. --> <attr name="popupAnimationStyle" format="reference" /> <!-- Whether the popup window should overlap its anchor view. --> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 1eb89468fc64..2dd67ba53ada 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2231,6 +2231,7 @@ <public type="attr" name="buttonBarPositiveButtonStyle" /> <public type="attr" name="buttonBarNeutralButtonStyle" /> <public type="attr" name="buttonBarNegativeButtonStyle" /> + <public type="attr" name="popupElevation" /> <public-padding type="dimen" name="l_resource_pad" end="0x01050010" /> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index e8bd99ac239e..7c60c6ee9f9c 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -2152,8 +2152,8 @@ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_accessDrmCertificates">Allows an application to provision and use DRM certficates. Should never be needed for normal apps.</string> - <string name="permlab_handoverStatus">Receive handover transfer broadcasts.</string> - <string name="permdesc_handoverStatus">Allows receiving handover transfer status information.</string> + <string name="permlab_handoverStatus">Receive Android Beam transfer status</string> + <string name="permdesc_handoverStatus">Allows this application to receive information about current Android Beam transfers</string> <!-- Policy administration --> diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml index c1eb999da5d8..3880fb320ff0 100644 --- a/core/res/res/values/styles_material.xml +++ b/core/res/res/values/styles_material.xml @@ -733,6 +733,7 @@ please see styles_device_defaults.xml. <style name="Widget.Material.ListPopupWindow" parent="Widget.ListPopupWindow"> <item name="dropDownSelector">?attr/listChoiceBackgroundIndicator</item> <item name="popupBackground">@drawable/popup_background_material</item> + <item name="popupElevation">@dimen/floating_window_z</item> <item name="popupAnimationStyle">@style/Animation.Material.Popup</item> <item name="dropDownVerticalOffset">0dip</item> <item name="dropDownHorizontalOffset">0dip</item> diff --git a/data/fonts/fallback_fonts.xml b/data/fonts/fallback_fonts.xml index a7e159af8c35..0f0281bfd96c 100644 --- a/data/fonts/fallback_fonts.xml +++ b/data/fonts/fallback_fonts.xml @@ -234,12 +234,12 @@ </family> <family> <fileset> - <file lang="zh-CN">NotoSansHans-Regular.otf</file> + <file lang="zh-Hans">NotoSansHans-Regular.otf</file> </fileset> </family> <family> <fileset> - <file lang="zh-TW">NotoSansHant-Regular.otf</file> + <file lang="zh-Hant">NotoSansHant-Regular.otf</file> </fileset> </family> <family> diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java index b050a026521d..e7a073c2e944 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -1019,9 +1019,6 @@ public abstract class Drawable { drawable = new StateListDrawable(); } else if (name.equals("animated-selector")) { drawable = new AnimatedStateListDrawable(); - } else if (name.equals("material-progress")) { - // TODO: Replace this with something less ridiculous. - drawable = new MaterialProgressDrawable(); } else if (name.equals("level-list")) { drawable = new LevelListDrawable(); } else if (name.equals("layer-list")) { diff --git a/graphics/java/android/graphics/drawable/MaterialProgressDrawable.java b/graphics/java/android/graphics/drawable/MaterialProgressDrawable.java deleted file mode 100644 index c484094f0ff5..000000000000 --- a/graphics/java/android/graphics/drawable/MaterialProgressDrawable.java +++ /dev/null @@ -1,548 +0,0 @@ -/* - * 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.drawable; - -import android.animation.Animator; -import android.animation.ObjectAnimator; -import android.animation.TimeInterpolator; -import android.content.res.ColorStateList; -import android.content.res.Resources; -import android.content.res.Resources.Theme; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.ColorFilter; -import android.graphics.Paint; -import android.graphics.PorterDuffColorFilter; -import android.graphics.Paint.Cap; -import android.graphics.Paint.Style; -import android.graphics.PorterDuff.Mode; -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.graphics.RectF; -import android.util.AttributeSet; -import android.view.animation.AccelerateDecelerateInterpolator; -import android.view.animation.LinearInterpolator; - -import com.android.internal.R; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; -import java.util.ArrayList; - -/** - * Fancy progress indicator for Material theme. - * - * TODO: Replace this class with something less ridiculous. - */ -class MaterialProgressDrawable extends Drawable implements Animatable { - private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); - private static final TimeInterpolator END_CURVE_INTERPOLATOR = new EndCurveInterpolator(); - private static final TimeInterpolator START_CURVE_INTERPOLATOR = new StartCurveInterpolator(); - - /** The duration of a single progress spin in milliseconds. */ - private static final int ANIMATION_DURATION = 1000 * 80 / 60; - - /** The number of points in the progress "star". */ - private static final int NUM_POINTS = 5; - - /** The list of animators operating on this drawable. */ - private final ArrayList<Animator> mAnimators = new ArrayList<Animator>(); - - /** The indicator ring, used to manage animation state. */ - private final Ring mRing; - - private MaterialProgressState mState; - private PorterDuffColorFilter mTintFilter; - - /** Canvas rotation in degrees. */ - private float mRotation; - - private boolean mMutated; - - public MaterialProgressDrawable() { - this(new MaterialProgressState(null), null); - } - - private MaterialProgressDrawable(MaterialProgressState state, Theme theme) { - mState = state; - if (theme != null && state.canApplyTheme()) { - applyTheme(theme); - } - - mRing = new Ring(mCallback); - mMutated = false; - - initializeFromState(); - setupAnimators(); - } - - private void initializeFromState() { - final MaterialProgressState state = mState; - - final Ring ring = mRing; - ring.setStrokeWidth(state.mStrokeWidth); - - final int color = state.mColor.getColorForState(getState(), Color.TRANSPARENT); - ring.setColor(color); - - final float minEdge = Math.min(state.mWidth, state.mHeight); - if (state.mInnerRadius <= 0 || minEdge < 0) { - ring.setInsets((int) Math.ceil(state.mStrokeWidth / 2.0f)); - } else { - float insets = minEdge / 2.0f - state.mInnerRadius; - ring.setInsets(insets); - } - - mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); - } - - @Override - public Drawable mutate() { - if (!mMutated && super.mutate() == this) { - mState = new MaterialProgressState(mState); - mMutated = true; - } - return this; - } - - @Override - protected boolean onStateChange(int[] stateSet) { - boolean changed = super.onStateChange(stateSet); - - final MaterialProgressState state = mState; - final int color = state.mColor.getColorForState(stateSet, Color.TRANSPARENT); - if (color != mRing.getColor()) { - mRing.setColor(color); - changed = true; - } - - if (state.mTint != null && state.mTintMode != null) { - mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); - changed = true; - } - - return changed; - } - - @Override - public boolean isStateful() { - return super.isStateful() || mState.mColor.isStateful(); - } - - @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) - throws XmlPullParserException, IOException { - final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.MaterialProgressDrawable); - super.inflateWithAttributes(r, parser, a, R.styleable.MaterialProgressDrawable_visible); - updateStateFromTypedArray(a); - a.recycle(); - - initializeFromState(); - } - - @Override - public void applyTheme(Theme t) { - final TypedArray a = t.resolveAttributes(mState.mThemeAttrs, - R.styleable.MaterialProgressDrawable); - updateStateFromTypedArray(a); - a.recycle(); - } - - private void updateStateFromTypedArray(TypedArray a) { - final MaterialProgressState state = mState; - state.mThemeAttrs = a.extractThemeAttrs(); - state.mWidth = a.getDimensionPixelSize( - R.styleable.MaterialProgressDrawable_width, state.mWidth); - state.mHeight = a.getDimensionPixelSize( - R.styleable.MaterialProgressDrawable_height, state.mHeight); - state.mInnerRadius = a.getDimension( - R.styleable.MaterialProgressDrawable_innerRadius, state.mInnerRadius); - state.mStrokeWidth = a.getDimension( - R.styleable.MaterialProgressDrawable_thickness, state.mStrokeWidth); - - if (a.hasValue(R.styleable.MaterialProgressDrawable_color)) { - state.mColor = a.getColorStateList(R.styleable.MaterialProgressDrawable_color); - } - } - - @Override - public boolean setVisible(boolean visible, boolean restart) { - boolean changed = super.setVisible(visible, restart); - if (visible) { - if (changed || restart) { - start(); - } - } else { - stop(); - } - return changed; - } - - @Override - public int getIntrinsicHeight() { - return mState.mHeight; - } - - @Override - public int getIntrinsicWidth() { - return mState.mWidth; - } - - @Override - public void draw(Canvas c) { - final Rect bounds = getBounds(); - final int saveCount = c.save(); - c.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY()); - mRing.draw(c, bounds); - c.restoreToCount(saveCount); - } - - @Override - public void setAlpha(int alpha) { - mRing.setAlpha(alpha); - } - - @Override - public int getAlpha() { - return mRing.getAlpha(); - } - - @Override - public void setColorFilter(ColorFilter colorFilter) { - mRing.setColorFilter(colorFilter); - } - - @Override - public ColorFilter getColorFilter() { - return mRing.getColorFilter(); - } - - @Override - public void setTint(ColorStateList tint, Mode tintMode) { - if (mState.mTint != tint || mState.mTintMode != tintMode) { - mState.mTint = tint; - mState.mTintMode = tintMode; - - mTintFilter = updateTintFilter(mTintFilter, tint, tintMode); - invalidateSelf(); - } - } - - @SuppressWarnings("unused") - private void setRotation(float rotation) { - mRotation = rotation; - invalidateSelf(); - } - - @SuppressWarnings("unused") - private float getRotation() { - return mRotation; - } - - @Override - public int getOpacity() { - return PixelFormat.TRANSLUCENT; - } - - @Override - public boolean isRunning() { - final ArrayList<Animator> animators = mAnimators; - final int N = animators.size(); - for (int i = 0; i < N; i++) { - final Animator animator = animators.get(i); - if (animator.isRunning()) { - return true; - } - } - return false; - } - - @Override - public void start() { - final ArrayList<Animator> animators = mAnimators; - final int N = animators.size(); - for (int i = 0; i < N; i++) { - final Animator animator = animators.get(i); - if (animator.isPaused()) { - animator.resume(); - } else if (!animator.isRunning()){ - animator.start(); - } - } - } - - @Override - public void stop() { - final ArrayList<Animator> animators = mAnimators; - final int N = animators.size(); - for (int i = 0; i < N; i++) { - final Animator animator = animators.get(i); - animator.pause(); - } - } - - private void setupAnimators() { - final Ring ring = mRing; - - final ObjectAnimator endTrim = ObjectAnimator.ofFloat(ring, "endTrim", 0, 0.75f); - endTrim.setDuration(ANIMATION_DURATION); - endTrim.setInterpolator(START_CURVE_INTERPOLATOR); - endTrim.setRepeatCount(ObjectAnimator.INFINITE); - endTrim.setRepeatMode(ObjectAnimator.RESTART); - - final ObjectAnimator startTrim = ObjectAnimator.ofFloat(ring, "startTrim", 0.0f, 0.75f); - startTrim.setDuration(ANIMATION_DURATION); - startTrim.setInterpolator(END_CURVE_INTERPOLATOR); - startTrim.setRepeatCount(ObjectAnimator.INFINITE); - startTrim.setRepeatMode(ObjectAnimator.RESTART); - - final ObjectAnimator rotation = ObjectAnimator.ofFloat(ring, "rotation", 0.0f, 0.25f); - rotation.setDuration(ANIMATION_DURATION); - rotation.setInterpolator(LINEAR_INTERPOLATOR); - rotation.setRepeatCount(ObjectAnimator.INFINITE); - rotation.setRepeatMode(ObjectAnimator.RESTART); - - final ObjectAnimator groupRotation = ObjectAnimator.ofFloat(this, "rotation", 0.0f, 360.0f); - groupRotation.setDuration(NUM_POINTS * ANIMATION_DURATION); - groupRotation.setInterpolator(LINEAR_INTERPOLATOR); - groupRotation.setRepeatCount(ObjectAnimator.INFINITE); - groupRotation.setRepeatMode(ObjectAnimator.RESTART); - - mAnimators.add(endTrim); - mAnimators.add(startTrim); - mAnimators.add(rotation); - mAnimators.add(groupRotation); - } - - private final Callback mCallback = new Callback() { - @Override - public void invalidateDrawable(Drawable d) { - invalidateSelf(); - } - - @Override - public void scheduleDrawable(Drawable d, Runnable what, long when) { - scheduleSelf(what, when); - } - - @Override - public void unscheduleDrawable(Drawable d, Runnable what) { - unscheduleSelf(what); - } - }; - - private static class MaterialProgressState extends ConstantState { - private int[] mThemeAttrs = null; - private float mStrokeWidth = 5.0f; - private float mInnerRadius = -1.0f; - private int mWidth = -1; - private int mHeight = -1; - private ColorStateList mColor = ColorStateList.valueOf(Color.TRANSPARENT); - private ColorStateList mTint = null; - private Mode mTintMode = null; - - public MaterialProgressState(MaterialProgressState orig) { - if (orig != null) { - mThemeAttrs = orig.mThemeAttrs; - mStrokeWidth = orig.mStrokeWidth; - mInnerRadius = orig.mInnerRadius; - mWidth = orig.mWidth; - mHeight = orig.mHeight; - mColor = orig.mColor; - mTint = orig.mTint; - mTintMode = orig.mTintMode; - } - } - - @Override - public boolean canApplyTheme() { - return mThemeAttrs != null; - } - - @Override - public Drawable newDrawable() { - return newDrawable(null, null); - } - - @Override - public Drawable newDrawable(Resources res) { - return newDrawable(res, null); - } - - @Override - public Drawable newDrawable(Resources res, Theme theme) { - return new MaterialProgressDrawable(this, theme); - } - - @Override - public int getChangingConfigurations() { - return 0; - } - } - - private static class Ring { - private final RectF mTempBounds = new RectF(); - private final Paint mPaint = new Paint(); - - private final Callback mCallback; - - private float mStartTrim = 0.0f; - private float mEndTrim = 0.0f; - private float mRotation = 0.0f; - private float mStrokeWidth = 5.0f; - private float mStrokeInset = 2.5f; - - private int mAlpha = 0xFF; - private int mColor = Color.BLACK; - - public Ring(Callback callback) { - mCallback = callback; - - mPaint.setStrokeCap(Cap.ROUND); - mPaint.setAntiAlias(true); - mPaint.setStyle(Style.STROKE); - } - - public void draw(Canvas c, Rect bounds) { - final RectF arcBounds = mTempBounds; - arcBounds.set(bounds); - arcBounds.inset(mStrokeInset, mStrokeInset); - - final float startAngle = (mStartTrim + mRotation) * 360; - final float endAngle = (mEndTrim + mRotation) * 360; - float sweepAngle = endAngle - startAngle; - - // Ensure the sweep angle isn't too small to draw. - final float diameter = Math.min(arcBounds.width(), arcBounds.height()); - final float minAngle = (float) (360.0 / (diameter * Math.PI)); - if (sweepAngle < minAngle && sweepAngle > -minAngle) { - sweepAngle = Math.signum(sweepAngle) * minAngle; - } - - c.drawArc(arcBounds, startAngle, sweepAngle, false, mPaint); - } - - public void setColorFilter(ColorFilter filter) { - mPaint.setColorFilter(filter); - invalidateSelf(); - } - - public ColorFilter getColorFilter() { - return mPaint.getColorFilter(); - } - - public void setAlpha(int alpha) { - mAlpha = alpha; - mPaint.setColor(mColor & 0xFFFFFF | alpha << 24); - invalidateSelf(); - } - - public int getAlpha() { - return mAlpha; - } - - public void setColor(int color) { - mColor = color; - mPaint.setColor(color & 0xFFFFFF | mAlpha << 24); - invalidateSelf(); - } - - public int getColor() { - return mColor; - } - - public void setStrokeWidth(float strokeWidth) { - mStrokeWidth = strokeWidth; - mPaint.setStrokeWidth(strokeWidth); - invalidateSelf(); - } - - @SuppressWarnings("unused") - public float getStrokeWidth() { - return mStrokeWidth; - } - - @SuppressWarnings("unused") - public void setStartTrim(float startTrim) { - mStartTrim = startTrim; - invalidateSelf(); - } - - @SuppressWarnings("unused") - public float getStartTrim() { - return mStartTrim; - } - - @SuppressWarnings("unused") - public void setEndTrim(float endTrim) { - mEndTrim = endTrim; - invalidateSelf(); - } - - @SuppressWarnings("unused") - public float getEndTrim() { - return mEndTrim; - } - - @SuppressWarnings("unused") - public void setRotation(float rotation) { - mRotation = rotation; - invalidateSelf(); - } - - @SuppressWarnings("unused") - public float getRotation() { - return mRotation; - } - - public void setInsets(float insets) { - mStrokeInset = insets; - } - - @SuppressWarnings("unused") - public float getInsets() { - return mStrokeInset; - } - - private void invalidateSelf() { - mCallback.invalidateDrawable(null); - } - } - - /** - * Squishes the interpolation curve into the second half of the animation. - */ - private static class EndCurveInterpolator extends AccelerateDecelerateInterpolator { - @Override - public float getInterpolation(float input) { - return super.getInterpolation(Math.max(0, (input - 0.5f) * 2.0f)); - } - } - - /** - * Squishes the interpolation curve into the first half of the animation. - */ - private static class StartCurveInterpolator extends AccelerateDecelerateInterpolator { - @Override - public float getInterpolation(float input) { - return super.getInterpolation(Math.min(1, input * 2.0f)); - } - } -} diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java index 4ea00461a9cd..8783994cf7ed 100644 --- a/graphics/java/android/graphics/drawable/VectorDrawable.java +++ b/graphics/java/android/graphics/drawable/VectorDrawable.java @@ -299,11 +299,10 @@ public class VectorDrawable extends Drawable { return color; } - @Override public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { - final TypedArray a = obtainAttributes(res, theme, attrs,R.styleable.VectorDrawable); + final TypedArray a = obtainAttributes(res, theme, attrs, R.styleable.VectorDrawable); updateStateFromTypedArray(a); a.recycle(); @@ -358,7 +357,7 @@ public class VectorDrawable extends Drawable { if (SHAPE_PATH.equals(tagName)) { final VPath path = new VPath(); path.inflate(res, attrs, theme); - currentGroup.add(path); + currentGroup.mChildren.add(path); if (path.getPathName() != null) { pathRenderer.mVGTargetsMap.put(path.getPathName(), path); } @@ -375,7 +374,7 @@ public class VectorDrawable extends Drawable { } else if (SHAPE_GROUP.equals(tagName)) { VGroup newChildGroup = new VGroup(); newChildGroup.inflate(res, attrs, theme); - currentGroup.mChildGroupList.add(newChildGroup); + currentGroup.mChildren.add(newChildGroup); groupStack.push(newChildGroup); if (newChildGroup.getGroupName() != null) { pathRenderer.mVGTargetsMap.put(newChildGroup.getGroupName(), @@ -424,16 +423,19 @@ public class VectorDrawable extends Drawable { private void printGroupTree(VGroup currentGroup, int level) { String indent = ""; - for (int i = 0 ; i < level ; i++) { + for (int i = 0; i < level; i++) { indent += " "; } // Print the current node - Log.v(LOGTAG, indent + "current group is :" + currentGroup.getGroupName() + Log.v(LOGTAG, indent + "current group is :" + currentGroup.getGroupName() + " rotation is " + currentGroup.mRotate); - Log.v(LOGTAG, indent + "matrix is :" + currentGroup.getLocalMatrix().toString()); - // Then print all the children - for (int i = 0 ; i < currentGroup.mChildGroupList.size(); i++) { - printGroupTree(currentGroup.mChildGroupList.get(i), level + 1); + Log.v(LOGTAG, indent + "matrix is :" + currentGroup.getLocalMatrix().toString()); + // Then print all the children groups + for (int i = 0; i < currentGroup.mChildren.size(); i++) { + Object child = currentGroup.mChildren.get(i); + if (child instanceof VGroup) { + printGroupTree((VGroup) child, level + 1); + } } } @@ -552,21 +554,21 @@ public class VectorDrawable extends Drawable { private boolean recursiveCanApplyTheme(VGroup currentGroup) { // We can do a tree traverse here, if there is one path return true, // then we return true for the whole tree. - final ArrayList<VPath> paths = currentGroup.mPathList; - for (int j = paths.size() - 1; j >= 0; j--) { - final VPath path = paths.get(j); - if (path.canApplyTheme()) { - return true; - } - } - - final ArrayList<VGroup> childGroups = currentGroup.mChildGroupList; - - for (int i = 0; i < childGroups.size(); i++) { - VGroup childGroup = childGroups.get(i); - if (childGroup.canApplyTheme() - || recursiveCanApplyTheme(childGroup)) { - return true; + final ArrayList<Object> children = currentGroup.mChildren; + + for (int i = 0; i < children.size(); i++) { + Object child = children.get(i); + if (child instanceof VGroup) { + VGroup childGroup = (VGroup) child; + if (childGroup.canApplyTheme() + || recursiveCanApplyTheme(childGroup)) { + return true; + } + } else if (child instanceof VPath) { + VPath childPath = (VPath) child; + if (childPath.canApplyTheme()) { + return true; + } } } return false; @@ -580,24 +582,22 @@ public class VectorDrawable extends Drawable { private void recursiveApplyTheme(VGroup currentGroup, Theme t) { // We can do a tree traverse here, apply theme to all paths which // can apply theme. - final ArrayList<VPath> paths = currentGroup.mPathList; - for (int j = paths.size() - 1; j >= 0; j--) { - final VPath path = paths.get(j); - if (path.canApplyTheme()) { - path.applyTheme(t); - } - } - - final ArrayList<VGroup> childGroups = currentGroup.mChildGroupList; - - for (int i = 0; i < childGroups.size(); i++) { - VGroup childGroup = childGroups.get(i); - if (childGroup.canApplyTheme()) { - childGroup.applyTheme(t); + final ArrayList<Object> children = currentGroup.mChildren; + for (int i = 0; i < children.size(); i++) { + Object child = children.get(i); + if (child instanceof VGroup) { + VGroup childGroup = (VGroup) child; + if (childGroup.canApplyTheme()) { + childGroup.applyTheme(t); + } + recursiveApplyTheme(childGroup, t); + } else if (child instanceof VPath) { + VPath childPath = (VPath) child; + if (childPath.canApplyTheme()) { + childPath.applyTheme(t); + } } - recursiveApplyTheme(childGroup, t); } - } public void setColorFilter(ColorFilter colorFilter) { @@ -624,11 +624,18 @@ public class VectorDrawable extends Drawable { currentGroup.mStackedMatrix.preConcat(currentGroup.mLocalMatrix); float stackedAlpha = currentAlpha * currentGroup.mGroupAlpha; - drawPath(currentGroup, stackedAlpha, canvas, w, h); - // Draw the group tree in post order. - for (int i = 0 ; i < currentGroup.mChildGroupList.size(); i++) { - drawGroupTree(currentGroup.mChildGroupList.get(i), - currentGroup.mStackedMatrix, stackedAlpha, canvas, w, h); + + // Draw the group tree in the same order as the XML file. + for (int i = 0; i < currentGroup.mChildren.size(); i++) { + Object child = currentGroup.mChildren.get(i); + if (child instanceof VGroup) { + VGroup childGroup = (VGroup) child; + drawGroupTree(childGroup, currentGroup.mStackedMatrix, + stackedAlpha, canvas, w, h); + } else if (child instanceof VPath) { + VPath childPath = (VPath) child; + drawPath(currentGroup, childPath, stackedAlpha, canvas, w, h); + } } } @@ -637,7 +644,8 @@ public class VectorDrawable extends Drawable { drawGroupTree(mRootGroup, IDENTITY_MATRIX, ((float) mRootAlpha) / 0xFF, canvas, w, h); } - private void drawPath(VGroup vGroup, float stackedAlpha, Canvas canvas, int w, int h) { + private void drawPath(VGroup vGroup, VPath vPath, float stackedAlpha, + Canvas canvas, int w, int h) { final float scale = Math.min(h / mViewportHeight, w / mViewportWidth); mFinalPathMatrix.set(vGroup.mStackedMatrix); @@ -645,75 +653,71 @@ public class VectorDrawable extends Drawable { mFinalPathMatrix.postTranslate( w / 2f - mViewportWidth / 2f, h / 2f - mViewportHeight / 2f); - ArrayList<VPath> paths = vGroup.getPaths(); - for (int i = 0; i < paths.size(); i++) { - VPath vPath = paths.get(i); - vPath.toPath(mPath); - final Path path = mPath; + vPath.toPath(mPath); + final Path path = mPath; - if (vPath.mTrimPathStart != 0.0f || vPath.mTrimPathEnd != 1.0f) { - float start = (vPath.mTrimPathStart + vPath.mTrimPathOffset) % 1.0f; - float end = (vPath.mTrimPathEnd + vPath.mTrimPathOffset) % 1.0f; + if (vPath.mTrimPathStart != 0.0f || vPath.mTrimPathEnd != 1.0f) { + float start = (vPath.mTrimPathStart + vPath.mTrimPathOffset) % 1.0f; + float end = (vPath.mTrimPathEnd + vPath.mTrimPathOffset) % 1.0f; - if (mPathMeasure == null) { - mPathMeasure = new PathMeasure(); - } - mPathMeasure.setPath(mPath, false); - - float len = mPathMeasure.getLength(); - start = start * len; - end = end * len; - path.reset(); - if (start > end) { - mPathMeasure.getSegment(start, len, path, true); - mPathMeasure.getSegment(0f, end, path, true); - } else { - mPathMeasure.getSegment(start, end, path, true); - } - path.rLineTo(0, 0); // fix bug in measure + if (mPathMeasure == null) { + mPathMeasure = new PathMeasure(); } + mPathMeasure.setPath(mPath, false); + + float len = mPathMeasure.getLength(); + start = start * len; + end = end * len; + path.reset(); + if (start > end) { + mPathMeasure.getSegment(start, len, path, true); + mPathMeasure.getSegment(0f, end, path, true); + } else { + mPathMeasure.getSegment(start, end, path, true); + } + path.rLineTo(0, 0); // fix bug in measure + } - mRenderPath.reset(); + mRenderPath.reset(); - mRenderPath.addPath(path, mFinalPathMatrix); + mRenderPath.addPath(path, mFinalPathMatrix); - if (vPath.mClip) { - canvas.clipPath(mRenderPath, Region.Op.REPLACE); - } else { - if (vPath.mFillColor != 0) { - if (mFillPaint == null) { - mFillPaint = new Paint(); - mFillPaint.setColorFilter(mColorFilter); - mFillPaint.setStyle(Paint.Style.FILL); - mFillPaint.setAntiAlias(true); - } - mFillPaint.setColor(applyAlpha(vPath.mFillColor, stackedAlpha)); - canvas.drawPath(mRenderPath, mFillPaint); + if (vPath.mClip) { + canvas.clipPath(mRenderPath, Region.Op.REPLACE); + } else { + if (vPath.mFillColor != 0) { + if (mFillPaint == null) { + mFillPaint = new Paint(); + mFillPaint.setColorFilter(mColorFilter); + mFillPaint.setStyle(Paint.Style.FILL); + mFillPaint.setAntiAlias(true); } + mFillPaint.setColor(applyAlpha(vPath.mFillColor, stackedAlpha)); + canvas.drawPath(mRenderPath, mFillPaint); + } - if (vPath.mStrokeColor != 0) { - if (mStrokePaint == null) { - mStrokePaint = new Paint(); - mStrokePaint.setColorFilter(mColorFilter); - mStrokePaint.setStyle(Paint.Style.STROKE); - mStrokePaint.setAntiAlias(true); - } + if (vPath.mStrokeColor != 0) { + if (mStrokePaint == null) { + mStrokePaint = new Paint(); + mStrokePaint.setColorFilter(mColorFilter); + mStrokePaint.setStyle(Paint.Style.STROKE); + mStrokePaint.setAntiAlias(true); + } - final Paint strokePaint = mStrokePaint; - if (vPath.mStrokeLineJoin != null) { - strokePaint.setStrokeJoin(vPath.mStrokeLineJoin); - } + final Paint strokePaint = mStrokePaint; + if (vPath.mStrokeLineJoin != null) { + strokePaint.setStrokeJoin(vPath.mStrokeLineJoin); + } - if (vPath.mStrokeLineCap != null) { - strokePaint.setStrokeCap(vPath.mStrokeLineCap); - } + if (vPath.mStrokeLineCap != null) { + strokePaint.setStrokeCap(vPath.mStrokeLineCap); + } - strokePaint.setStrokeMiter(vPath.mStrokeMiterlimit * scale); + strokePaint.setStrokeMiter(vPath.mStrokeMiterlimit * scale); - strokePaint.setColor(applyAlpha(vPath.mStrokeColor, stackedAlpha)); - strokePaint.setStrokeWidth(vPath.mStrokeWidth * scale); - canvas.drawPath(mRenderPath, strokePaint); - } + strokePaint.setColor(applyAlpha(vPath.mStrokeColor, stackedAlpha)); + strokePaint.setStrokeWidth(vPath.mStrokeWidth * scale); + canvas.drawPath(mRenderPath, strokePaint); } } } @@ -742,7 +746,7 @@ public class VectorDrawable extends Drawable { } private void parseSize(Resources r, AttributeSet attrs) - throws XmlPullParserException { + throws XmlPullParserException { final TypedArray a = r.obtainAttributes(attrs, R.styleable.VectorDrawableSize); // Account for any configuration changes. @@ -771,8 +775,7 @@ public class VectorDrawable extends Drawable { ///////////////////////////////////////////////////// // Variables below need to be copied (deep copy if applicable) for mutation. - private final ArrayList<VPath> mPathList = new ArrayList<VPath>(); - private final ArrayList<VGroup> mChildGroupList = new ArrayList<VGroup>(); + final ArrayList<Object> mChildren = new ArrayList<Object>(); private float mRotate = 0; private float mPivotX = 0; @@ -808,19 +811,21 @@ public class VectorDrawable extends Drawable { mLocalMatrix.set(copy.mLocalMatrix); - for (int i = 0; i < copy.mPathList.size(); i ++) { - VPath copyPath = copy.mPathList.get(i); - VPath newPath = new VPath(copyPath); - mPathList.add(newPath); - if (newPath.mPathName != null) { - targetsMap.put(copyPath.mPathName, newPath); + final ArrayList<Object> children = copy.mChildren; + for (int i = 0; i < children.size(); i++) { + Object copyChild = children.get(i); + if (copyChild instanceof VGroup) { + VGroup copyGroup = (VGroup) copyChild; + mChildren.add(new VGroup(copyGroup, targetsMap)); + } else if (copyChild instanceof VPath) { + VPath copyPath = (VPath) copyChild; + VPath newPath = new VPath(copyPath); + mChildren.add(newPath); + if (newPath.mPathName != null) { + targetsMap.put(newPath.mPathName, newPath); + } } } - - for (int i = 0; i < copy.mChildGroupList.size(); i ++) { - VGroup currentGroup = copy.mChildGroupList.get(i); - mChildGroupList.add(new VGroup(currentGroup, targetsMap)); - } } public VGroup() { @@ -922,10 +927,6 @@ public class VectorDrawable extends Drawable { return mLocalMatrix; } - public void add(VPath path) { - mPathList.add(path); - } - public boolean canApplyTheme() { return mThemeAttrs != null; } @@ -980,15 +981,6 @@ public class VectorDrawable extends Drawable { mLocalMatrix.postRotate(mRotate, 0, 0); mLocalMatrix.postTranslate(mTranslateX + mPivotX, mTranslateY + mPivotY); } - - /** - * Must return in order of adding - * @return ordered list of paths - */ - public ArrayList<VPath> getPaths() { - return mPathList; - } - } private static class VPath { @@ -1012,7 +1004,7 @@ public class VectorDrawable extends Drawable { float mStrokeMiterlimit = 4; private PathParser.PathDataNode[] mNodes = null; - private String mPathName; + String mPathName; private int mChangingConfigurations; public VPath() { diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index ffbe2cb8c6cf..5bcbc3c3898d 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -346,6 +346,12 @@ public class AudioManager { public static final int FLAG_HDMI_SYSTEM_AUDIO_VOLUME = 1 << 8; /** + * Indicates that this should only be handled if media is actively playing. + * @hide + */ + public static final int FLAG_ACTIVE_MEDIA_ONLY = 1 << 9; + + /** * Ringer mode that will be silent and will not vibrate. (This overrides the * vibrate setting.) * diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index f262390ae3e8..7316e14fc6a2 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -850,11 +850,10 @@ public class AudioService extends IAudioService.Stub { streamType = getActiveStreamType(suggestedStreamType); } - // Play sounds on STREAM_RING only and if lock screen is not on. + // Play sounds on STREAM_RING and STREAM_REMOTE_MUSIC only. if ((streamType != STREAM_REMOTE_MUSIC) && (flags & AudioManager.FLAG_PLAY_SOUND) != 0 && - ((mStreamVolumeAlias[streamType] != AudioSystem.STREAM_RING) - || (mKeyguardManager != null && mKeyguardManager.isKeyguardLocked()))) { + (mStreamVolumeAlias[streamType] != AudioSystem.STREAM_RING)) { flags &= ~AudioManager.FLAG_PLAY_SOUND; } diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java index dbe13ddbe234..2cbdc96eefe9 100644 --- a/media/java/android/media/session/MediaSession.java +++ b/media/java/android/media/session/MediaSession.java @@ -699,6 +699,25 @@ public final class MediaSession { } /** + * Return true if this is considered an active playback state. + * + * @hide + */ + public static boolean isActiveState(int state) { + switch (state) { + case PlaybackState.STATE_FAST_FORWARDING: + case PlaybackState.STATE_REWINDING: + case PlaybackState.STATE_SKIPPING_TO_PREVIOUS: + case PlaybackState.STATE_SKIPPING_TO_NEXT: + case PlaybackState.STATE_BUFFERING: + case PlaybackState.STATE_CONNECTING: + case PlaybackState.STATE_PLAYING: + return true; + } + return false; + } + + /** * Represents an ongoing session. This may be passed to apps by the session * owner to allow them to create a {@link MediaController} to communicate with * the session. diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java index 0b04485bc67d..5d1a7e0280fc 100644 --- a/media/java/android/media/session/MediaSessionLegacyHelper.java +++ b/media/java/android/media/session/MediaSessionLegacyHelper.java @@ -21,6 +21,7 @@ import android.app.PendingIntent.CanceledException; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.media.AudioManager; import android.media.MediaMetadata; import android.media.MediaMetadataEditor; import android.media.MediaMetadataRetriever; @@ -157,12 +158,59 @@ public class MediaSessionLegacyHelper { } public void sendMediaButtonEvent(KeyEvent keyEvent, boolean needWakeLock) { + if (keyEvent == null) { + Log.w(TAG, "Tried to send a null key event. Ignoring."); + return; + } mSessionManager.dispatchMediaKeyEvent(keyEvent, needWakeLock); if (DEBUG) { Log.d(TAG, "dispatched media key " + keyEvent); } } + public void sendVolumeKeyEvent(KeyEvent keyEvent, boolean musicOnly) { + if (keyEvent == null) { + Log.w(TAG, "Tried to send a null key event. Ignoring."); + return; + } + boolean down = keyEvent.getAction() == KeyEvent.ACTION_DOWN; + boolean up = keyEvent.getAction() == KeyEvent.ACTION_UP; + int direction = 0; + switch (keyEvent.getKeyCode()) { + case KeyEvent.KEYCODE_VOLUME_UP: + direction = AudioManager.ADJUST_RAISE; + break; + case KeyEvent.KEYCODE_VOLUME_DOWN: + direction = AudioManager.ADJUST_LOWER; + break; + case KeyEvent.KEYCODE_VOLUME_MUTE: + // TODO + break; + } + if ((down || up) && direction != 0) { + int flags; + // If this is action up we want to send a beep for non-music events + if (up) { + direction = 0; + } + if (musicOnly) { + // This flag is used when the screen is off to only affect + // active media + flags = AudioManager.FLAG_ACTIVE_MEDIA_ONLY; + } else { + // These flags are consistent with the home screen + if (up) { + flags = AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE; + } else { + flags = AudioManager.FLAG_SHOW_UI; + } + } + + mSessionManager.dispatchAdjustVolumeBy(AudioManager.USE_DEFAULT_STREAM_TYPE, + direction, flags); + } + } + public void sendAdjustVolumeBy(int suggestedStream, int delta, int flags) { mSessionManager.dispatchAdjustVolumeBy(suggestedStream, delta, flags); if (DEBUG) { diff --git a/packages/CaptivePortalLogin/Android.mk b/packages/CaptivePortalLogin/Android.mk new file mode 100644 index 000000000000..576debc6478d --- /dev/null +++ b/packages/CaptivePortalLogin/Android.mk @@ -0,0 +1,11 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_PACKAGE_NAME := CaptivePortalLogin +LOCAL_CERTIFICATE := platform + +include $(BUILD_PACKAGE) diff --git a/packages/CaptivePortalLogin/AndroidManifest.xml b/packages/CaptivePortalLogin/AndroidManifest.xml new file mode 100644 index 000000000000..5f78afe5601b --- /dev/null +++ b/packages/CaptivePortalLogin/AndroidManifest.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * 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. + */ +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.captiveportallogin" > + + <uses-permission android:name="android.permission.INTERNET" /> + + <application android:label="@string/app_name" > + <activity + android:name="com.android.captiveportallogin.CaptivePortalLoginActivity" + android:label="@string/action_bar_label" + android:theme="@android:style/Theme.Holo" > + <intent-filter> + <action android:name="android.intent.action.ACTION_SEND"/> + <category android:name="android.intent.category.DEFAULT"/> + <data android:mimeType="text/plain"/> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/packages/CaptivePortalLogin/res/layout/activity_captive_portal_login.xml b/packages/CaptivePortalLogin/res/layout/activity_captive_portal_login.xml new file mode 100644 index 000000000000..d8f2928daec9 --- /dev/null +++ b/packages/CaptivePortalLogin/res/layout/activity_captive_portal_login.xml @@ -0,0 +1,20 @@ +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/container" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context="com.android.captiveportallogin.CaptivePortalLoginActivity" + tools:ignore="MergeRootFrame"> + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="match_parent" > + + <WebView + android:id="@+id/webview" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_alignParentBottom="false" + android:layout_alignParentRight="false" /> + +</RelativeLayout> +</FrameLayout> diff --git a/packages/CaptivePortalLogin/res/menu/captive_portal_login.xml b/packages/CaptivePortalLogin/res/menu/captive_portal_login.xml new file mode 100644 index 000000000000..1a88c5c846cc --- /dev/null +++ b/packages/CaptivePortalLogin/res/menu/captive_portal_login.xml @@ -0,0 +1,15 @@ +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + tools:context="com.android.captiveportallogin.CaptivePortalLoginActivity" > + <item + android:id="@+id/action_do_not_use_network" + android:orderInCategory="100" + android:showAsAction="never" + android:title="@string/action_do_not_use_network"/> + <item + android:id="@+id/action_use_network" + android:orderInCategory="200" + android:showAsAction="never" + android:title="@string/action_use_network"/> + +</menu> diff --git a/packages/CaptivePortalLogin/res/values/dimens.xml b/packages/CaptivePortalLogin/res/values/dimens.xml new file mode 100644 index 000000000000..55c1e5908c7e --- /dev/null +++ b/packages/CaptivePortalLogin/res/values/dimens.xml @@ -0,0 +1,7 @@ +<resources> + + <!-- Default screen margins, per the Android Design guidelines. --> + <dimen name="activity_horizontal_margin">16dp</dimen> + <dimen name="activity_vertical_margin">16dp</dimen> + +</resources> diff --git a/packages/CaptivePortalLogin/res/values/strings.xml b/packages/CaptivePortalLogin/res/values/strings.xml new file mode 100644 index 000000000000..1b0f0a481edd --- /dev/null +++ b/packages/CaptivePortalLogin/res/values/strings.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <string name="app_name">CaptivePortalLogin</string> + <string name="action_use_network">Use this network as is</string> + <string name="action_do_not_use_network">Do not use this network</string> + <string name="action_bar_label">Sign-in to network</string> + +</resources> diff --git a/packages/CaptivePortalLogin/res/values/styles.xml b/packages/CaptivePortalLogin/res/values/styles.xml new file mode 100644 index 000000000000..6ce89c7ba439 --- /dev/null +++ b/packages/CaptivePortalLogin/res/values/styles.xml @@ -0,0 +1,20 @@ +<resources> + + <!-- + Base application theme, dependent on API level. This theme is replaced + by AppBaseTheme from res/values-vXX/styles.xml on newer devices. + --> + <style name="AppBaseTheme" parent="android:Theme.Light"> + <!-- + Theme customizations available in newer API levels can go in + res/values-vXX/styles.xml, while customizations related to + backward-compatibility can go here. + --> + </style> + + <!-- Application theme. --> + <style name="AppTheme" parent="AppBaseTheme"> + <!-- All customizations that are NOT specific to a particular API-level can go here. --> + </style> + +</resources> diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java new file mode 100644 index 000000000000..2c1db02d6e7d --- /dev/null +++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java @@ -0,0 +1,160 @@ +/* + * 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.captiveportallogin; + +import android.app.Activity; +import android.content.Intent; +import android.graphics.Bitmap; +import android.net.ConnectivityManager; +import android.net.Network; +import android.os.Bundle; +import android.provider.Settings; +import android.provider.Settings.Global; +import android.view.Menu; +import android.view.MenuItem; +import android.view.Window; +import android.webkit.WebChromeClient; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.lang.InterruptedException; + +public class CaptivePortalLoginActivity extends Activity { + private static final String DEFAULT_SERVER = "clients3.google.com"; + private static final int SOCKET_TIMEOUT_MS = 10000; + + // Keep this in sync with NetworkMonitor. + // Intent broadcast to ConnectivityService indicating sign-in is complete. + // Extras: + // EXTRA_TEXT = netId + // LOGGED_IN_RESULT = "1" if we should use network, "0" if not. + private static final String ACTION_CAPTIVE_PORTAL_LOGGED_IN = + "android.net.netmon.captive_portal_logged_in"; + private static final String LOGGED_IN_RESULT = "result"; + + private URL mURL; + private int mNetId; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + String server = Settings.Global.getString(getContentResolver(), "captive_portal_server"); + if (server == null) server = DEFAULT_SERVER; + try { + mURL = new URL("http://" + server + "/generate_204"); + } catch (MalformedURLException e) { + done(true); + } + + requestWindowFeature(Window.FEATURE_PROGRESS); + setContentView(R.layout.activity_captive_portal_login); + + getActionBar().setDisplayShowHomeEnabled(false); + + mNetId = Integer.parseInt(getIntent().getStringExtra(Intent.EXTRA_TEXT)); + ConnectivityManager.setProcessDefaultNetwork(new Network(mNetId)); + + WebView myWebView = (WebView) findViewById(R.id.webview); + WebSettings webSettings = myWebView.getSettings(); + webSettings.setJavaScriptEnabled(true); + myWebView.setWebViewClient(new MyWebViewClient()); + myWebView.setWebChromeClient(new MyWebChromeClient()); + myWebView.loadUrl(mURL.toString()); + } + + private void done(boolean use_network) { + Intent intent = new Intent(ACTION_CAPTIVE_PORTAL_LOGGED_IN); + intent.putExtra(Intent.EXTRA_TEXT, String.valueOf(mNetId)); + intent.putExtra(LOGGED_IN_RESULT, use_network ? "1" : "0"); + sendBroadcast(intent); + finish(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.captive_portal_login, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + if (id == R.id.action_use_network) { + done(true); + return true; + } + if (id == R.id.action_do_not_use_network) { + done(false); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void testForCaptivePortal() { + new Thread(new Runnable() { + public void run() { + // Give time for captive portal to open. + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + } + HttpURLConnection urlConnection = null; + int httpResponseCode = 500; + try { + urlConnection = (HttpURLConnection) mURL.openConnection(); + urlConnection.setInstanceFollowRedirects(false); + urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS); + urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS); + urlConnection.setUseCaches(false); + urlConnection.getInputStream(); + httpResponseCode = urlConnection.getResponseCode(); + } catch (IOException e) { + } finally { + if (urlConnection != null) urlConnection.disconnect(); + } + if (httpResponseCode == 204) { + done(true); + } + } + }).start(); + } + + private class MyWebViewClient extends WebViewClient { + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + testForCaptivePortal(); + } + + @Override + public void onPageFinished(WebView view, String url) { + testForCaptivePortal(); + } + } + + private class MyWebChromeClient extends WebChromeClient { + @Override + public void onProgressChanged(WebView view, int newProgress) { + setProgress(newProgress*100); + } + } +} diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java index 67ed97cf51d0..20a621bd8084 100644 --- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java +++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java @@ -50,6 +50,7 @@ import com.android.internal.content.PackageHelper; import com.android.internal.os.IParcelFileDescriptorFactory; import com.android.internal.util.ArrayUtils; +import dalvik.system.VMRuntime; import libcore.io.IoUtils; import libcore.io.Streams; @@ -186,6 +187,7 @@ public class DefaultContainerService extends IntentService { ret.verifiers = pkg.verifiers; ret.recommendedInstallLocation = recommendAppInstallLocation(pkg, flags, threshold, abiOverride); + ret.multiArch = pkg.multiArch; return ret; } @@ -363,31 +365,26 @@ public class DefaultContainerService extends IntentService { final String resFileName = "pkg.apk"; final String publicResFileName = "res.zip"; - // The .apk file - String codePath = pkg.baseCodePath; - File codeFile = new File(codePath); - - String[] abiList = Build.SUPPORTED_ABIS; - if (abiOverride != null) { - abiList = new String[] { abiOverride }; - } else { - try { - if (Build.SUPPORTED_64_BIT_ABIS.length > 0 && - NativeLibraryHelper.hasRenderscriptBitcode(handle)) { - abiList = Build.SUPPORTED_32_BIT_ABIS; - } - } catch (IOException ioe) { - Slog.w(TAG, "Problem determining ABI for: " + codeFile.getPath()); - return null; - } + if (pkg.multiArch) { + // TODO: Support multiArch installs on ASEC. + throw new IllegalArgumentException("multiArch not supported on ASEC installs."); } - final int abiIndex = NativeLibraryHelper.findSupportedAbi(handle, abiList); + // The .apk file + final String codePath = pkg.baseCodePath; + final File codeFile = new File(codePath); + final String[] abis; + try { + abis = calculateAbiList(handle, abiOverride, pkg.multiArch); + } catch (IOException ioe) { + Slog.w(TAG, "Problem determining app ABIS: " + ioe); + return null; + } // Calculate size of container needed to hold base APK. final int sizeMb; try { - sizeMb = calculateContainerSize(pkg, handle, isForwardLocked, abiIndex); + sizeMb = calculateContainerSize(pkg, handle, isForwardLocked, abis); } catch (IOException e) { Slog.w(TAG, "Problem when trying to copy " + codeFile.getPath()); return null; @@ -451,17 +448,18 @@ public class DefaultContainerService extends IntentService { final File sharedLibraryDir = new File(newCachePath, LIB_DIR_NAME); if (sharedLibraryDir.mkdir()) { int ret = PackageManager.INSTALL_SUCCEEDED; - if (abiIndex >= 0) { + if (abis != null) { + // TODO(multiArch): Support multi-arch installs on asecs. Note that we are NOT + // using an ISA specific subdir here for now. + final String abi = abis[0]; ret = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle, - sharedLibraryDir, abiList[abiIndex]); - } else if (abiIndex != PackageManager.NO_NATIVE_LIBRARIES) { - ret = abiIndex; - } + sharedLibraryDir, abi); - if (ret != PackageManager.INSTALL_SUCCEEDED) { - Slog.e(TAG, "Could not copy native libraries to " + sharedLibraryDir.getPath()); - PackageHelper.destroySdDir(newCid); - return null; + if (ret != PackageManager.INSTALL_SUCCEEDED) { + Slog.e(TAG, "Could not copy native libraries to " + sharedLibraryDir.getPath()); + PackageHelper.destroySdDir(newCid); + return null; + } } } else { Slog.e(TAG, "Could not create native lib directory: " + sharedLibraryDir.getPath()); @@ -687,14 +685,53 @@ public class DefaultContainerService extends IntentService { NativeLibraryHelper.Handle handle = null; try { handle = NativeLibraryHelper.Handle.create(pkg); - final int abi = NativeLibraryHelper.findSupportedAbi(handle, - (abiOverride != null) ? new String[] { abiOverride } : Build.SUPPORTED_ABIS); - return calculateContainerSize(pkg, handle, isForwardLocked, abi); + return calculateContainerSize(pkg, handle, isForwardLocked, + calculateAbiList(handle, abiOverride, pkg.multiArch)); } finally { IoUtils.closeQuietly(handle); } } + private String[] calculateAbiList(NativeLibraryHelper.Handle handle, String abiOverride, + boolean isMultiArch) throws IOException { + if (isMultiArch) { + final int abi32 = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_32_BIT_ABIS); + final int abi64 = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_64_BIT_ABIS); + + if (abi32 >= 0 && abi64 >= 0) { + return new String[] { Build.SUPPORTED_64_BIT_ABIS[abi64], Build.SUPPORTED_32_BIT_ABIS[abi32] }; + } else if (abi64 >= 0) { + return new String[] { Build.SUPPORTED_64_BIT_ABIS[abi64] }; + } else if (abi32 >= 0) { + return new String[] { Build.SUPPORTED_32_BIT_ABIS[abi32] }; + } + + if (abi64 != PackageManager.NO_NATIVE_LIBRARIES || abi32 != PackageManager.NO_NATIVE_LIBRARIES) { + throw new IOException("Error determining ABI list: errorCode=[" + abi32 + "," + abi64 + "]"); + } + + } else { + String[] abiList = Build.SUPPORTED_ABIS; + if (abiOverride != null) { + abiList = new String[] { abiOverride }; + } else if (Build.SUPPORTED_64_BIT_ABIS.length > 0 && + NativeLibraryHelper.hasRenderscriptBitcode(handle)) { + abiList = Build.SUPPORTED_32_BIT_ABIS; + } + + final int abi = NativeLibraryHelper.findSupportedAbi(handle,abiList); + if (abi >= 0) { + return new String[]{Build.SUPPORTED_ABIS[abi]}; + } + + if (abi != PackageManager.NO_NATIVE_LIBRARIES) { + throw new IOException("Error determining ABI list: errorCode=" + abi); + } + } + + return null; + } + /** * Calculate the container size for a package. * @@ -702,7 +739,7 @@ public class DefaultContainerService extends IntentService { * @throws IOException when there is a problem reading the file */ private int calculateContainerSize(PackageLite pkg, NativeLibraryHelper.Handle handle, - boolean isForwardLocked, int abiIndex) throws IOException { + boolean isForwardLocked, String[] abis) throws IOException { // Calculate size of container needed to hold APKs. long sizeBytes = 0; for (String codePath : pkg.getAllCodePaths()) { @@ -715,9 +752,8 @@ public class DefaultContainerService extends IntentService { // Check all the native files that need to be copied and add that to the // container size. - if (abiIndex >= 0) { - sizeBytes += NativeLibraryHelper.sumNativeBinariesLI(handle, - Build.SUPPORTED_ABIS[abiIndex]); + if (abis != null) { + sizeBytes += NativeLibraryHelper.sumNativeBinariesLI(handle, abis); } int sizeMb = (int) (sizeBytes >> 20); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index e6c6f9f3ac00..5dc7d26a1b4e 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -135,7 +135,7 @@ public class SettingsProvider extends ContentProvider { sRestrictedKeys.put(Settings.Secure.LOCATION_MODE, UserManager.DISALLOW_SHARE_LOCATION); sRestrictedKeys.put(Settings.Secure.LOCATION_PROVIDERS_ALLOWED, UserManager.DISALLOW_SHARE_LOCATION); - sRestrictedKeys.put(Settings.Global.INSTALL_NON_MARKET_APPS, + sRestrictedKeys.put(Settings.Secure.INSTALL_NON_MARKET_APPS, UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES); sRestrictedKeys.put(Settings.Global.ADB_ENABLED, UserManager.DISALLOW_DEBUGGING_FEATURES); sRestrictedKeys.put(Settings.Global.PACKAGE_VERIFIER_ENABLE, diff --git a/packages/SystemUI/res/layout/status_bar_expanded_header.xml b/packages/SystemUI/res/layout/status_bar_expanded_header.xml index 2fd7bc4f992a..8b79dbf877f8 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded_header.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded_header.xml @@ -69,7 +69,7 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="8dp" - android:paddingEnd="4dp" + android:paddingEnd="@dimen/battery_level_padding_end" android:textColor="#ffffff" android:textSize="12sp"/> </LinearLayout> diff --git a/packages/SystemUI/res/layout/zen_mode_panel.xml b/packages/SystemUI/res/layout/zen_mode_panel.xml index 9d959fa15e7b..16ee5c84dc73 100644 --- a/packages/SystemUI/res/layout/zen_mode_panel.xml +++ b/packages/SystemUI/res/layout/zen_mode_panel.xml @@ -79,15 +79,4 @@ android:orientation="vertical" android:paddingTop="3dp" /> - <TextView - android:id="@+id/zen_alarm_warning" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingBottom="8dp" - android:paddingLeft="@dimen/qs_panel_padding" - android:paddingRight="@dimen/qs_panel_padding" - android:paddingTop="8dp" - android:text="@string/zen_alarm_warning" - android:textAppearance="@style/TextAppearance.QS.Warning" /> - </com.android.systemui.volume.ZenModePanel>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index c4acbde1ed2a..540032689977 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -351,4 +351,10 @@ <!-- The font size of the time when collapsed in QS --> <dimen name="qs_time_collapsed_size">14sp</dimen> + + <!-- The font size of the time when expanded in QS --> + <dimen name="qs_time_expanded_size">20sp</dimen> + + <!-- Battery level padding end when in expanded QS (but not on Keyguard) --> + <dimen name="battery_level_padding_end">4dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index ff60f1d4fcb6..848fdf842b37 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -595,8 +595,8 @@ <!-- Description of the left direction in which one can to slide the handle in the Slide unlock screen. [CHAR LIMIT=NONE] --> <string name="description_direction_left">"Slide left for <xliff:g id="target_description" example="Unlock">%s</xliff:g>.</string> - <!-- Zen mode: Alarm warning. [CHAR LIMIT=40] --> - <string name="zen_alarm_warning">You won\'t hear alarms or timers</string> + <!-- Zen mode: No interruptions title, with a warning about alarms and timers. [CHAR LIMIT=60] --> + <string name="zen_no_interruptions_with_warning">No interruptions, including alarms and timers</string> <!-- Zen mode: No interruptions. [CHAR LIMIT=40] --> <string name="zen_no_interruptions">No interruptions</string> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 230433d78fbf..f99b68b22f05 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -70,7 +70,7 @@ <style name="TextAppearance.StatusBar.Expanded" parent="@*android:style/TextAppearance.StatusBar" /> <style name="TextAppearance.StatusBar.Expanded.Clock"> - <item name="android:textSize">20sp</item> + <item name="android:textSize">@dimen/qs_time_expanded_size</item> <item name="android:fontFamily">sans-serif-medium</item> <item name="android:textColor">#ffffff</item> </style> @@ -185,11 +185,6 @@ <item name="android:textColor">@color/qs_subhead</item> </style> - <style name="TextAppearance.QS.Warning"> - <item name="android:textSize">12sp</item> - <item name="android:textColor">@color/qs_subhead</item> - </style> - <style name="TextAppearance.QS.SegmentedButton"> <item name="android:textSize">12sp</item> <item name="android:textAllCaps">true</item> diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java index 57f4565f5409..d1efb5742ee2 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java @@ -106,6 +106,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView public void run() { // Mark Recents as no longer visible AlternateRecentsComponent.notifyVisibilityChanged(false); + mVisible = false; // Finish Recents if (mLaunchIntent != null) { if (mLaunchOpts != null) { @@ -170,7 +171,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - if (action.equals(Intent.ACTION_SCREEN_OFF)) { + if (action.equals(Intent.ACTION_SCREEN_OFF) && mVisible) { mFinishLaunchHomeRunnable.run(); } else if (action.equals(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED)) { // Refresh the search widget @@ -518,8 +519,6 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView if (mConfig.searchBarAppWidgetId >= 0) { mAppWidgetHost.stopListening(); } - - mVisible = false; } @Override @@ -641,6 +640,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView public void onTaskViewClicked() { // Mark recents as no longer visible AlternateRecentsComponent.notifyVisibilityChanged(false); + mVisible = false; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java index 9921c55ac6c4..ed3ebf52cc2a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java @@ -75,16 +75,18 @@ public class NotificationData { } private final ArrayList<Entry> mEntries = new ArrayList<Entry>(); - private RankingMap mRanking; + private RankingMap mRankingMap; + private final Ranking mTmpRanking = new Ranking(); private final Comparator<Entry> mRankingComparator = new Comparator<Entry>() { + private final Ranking mRankingA = new Ranking(); + private final Ranking mRankingB = new Ranking(); + @Override public int compare(Entry a, Entry b) { - if (mRanking != null) { - Ranking aRanking = mRanking.getRanking(a.key); - Ranking bRanking = mRanking.getRanking(b.key); - int aRank = aRanking != null ? aRanking.getRank() : -1; - int bRank = bRanking != null ? bRanking.getRank() : -1; - return aRank - bRank; + if (mRankingMap != null) { + mRankingMap.getRanking(a.key, mRankingA); + mRankingMap.getRanking(b.key, mRankingB); + return mRankingA.getRank() - mRankingB.getRank(); } final StatusBarNotification na = a.notification; @@ -138,7 +140,7 @@ public class NotificationData { public boolean isAmbient(String key) { // TODO: Remove when switching to NotificationListener. - if (mRanking == null) { + if (mRankingMap == null) { for (Entry entry : mEntries) { if (key.equals(entry.key)) { return entry.notification.getNotification().priority == @@ -146,15 +148,15 @@ public class NotificationData { } } } else { - Ranking ranking = mRanking.getRanking(key); - return ranking != null && ranking.isAmbient(); + mRankingMap.getRanking(key, mTmpRanking); + return mTmpRanking.isAmbient(); } return false; } private void updateRankingAndSort(RankingMap ranking) { if (ranking != null) { - mRanking = ranking; + mRankingMap = ranking; } Collections.sort(mEntries, mRankingComparator); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index e0a1ef12e7b5..55b30887dcb0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -126,6 +126,8 @@ public class NotificationPanelView extends PanelView implements private boolean mBlockTouches; private ArrayList<View> mSwipeTranslationViews = new ArrayList<>(); private int mNotificationScrimWaitDistance; + private boolean mTwoFingerQsExpand; + private boolean mTwoFingerQsExpandPossible; /** * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still @@ -487,7 +489,7 @@ public class NotificationPanelView extends PanelView implements if (mExpandedHeight != 0) { handleQsDown(event); } - if (mQsTracking || mQsExpanded) { + if (!mTwoFingerQsExpand && (mQsTracking || mQsExpanded)) { onQsTouch(event); if (!mConflictingQsExpansionGesture) { return true; @@ -497,6 +499,15 @@ public class NotificationPanelView extends PanelView implements || event.getActionMasked() == MotionEvent.ACTION_UP) { mConflictingQsExpansionGesture = false; } + if (event.getActionMasked() == MotionEvent.ACTION_DOWN && mExpandedHeight == 0) { + mTwoFingerQsExpandPossible = true; + } + if (mTwoFingerQsExpandPossible && event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN + && event.getPointerCount() == 2 + && event.getY(event.getActionIndex()) < mStatusBarMinHeight) { + mTwoFingerQsExpand = true; + requestPanelHeightUpdate(); + } super.onTouchEvent(event); return true; } @@ -842,7 +853,7 @@ public class NotificationPanelView extends PanelView implements min = Math.max(min, minHeight); } int maxHeight; - if (mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted) { + if (mTwoFingerQsExpand || mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted) { maxHeight = (int) calculatePanelHeightQsExpanded(); } else { int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin(); @@ -863,7 +874,7 @@ public class NotificationPanelView extends PanelView implements if (!mQsExpanded) { positionClockAndNotifications(); } - if (mQsExpanded && !mQsTracking && mQsExpansionAnimator == null + if (mTwoFingerQsExpand || mQsExpanded && !mQsTracking && mQsExpansionAnimator == null && !mQsExpansionFromOverscroll) { float panelHeightQsCollapsed = mNotificationStackScroller.getIntrinsicPadding() + mNotificationStackScroller.getMinStackHeight() @@ -1049,6 +1060,8 @@ public class NotificationPanelView extends PanelView implements mHeader.setListening(true); mQsPanel.setListening(true); } + mTwoFingerQsExpand = false; + mTwoFingerQsExpandPossible = false; } @Override @@ -1060,7 +1073,7 @@ public class NotificationPanelView extends PanelView implements @Override protected void setOverExpansion(float overExpansion, boolean isPixels) { - if (mConflictingQsExpansionGesture) { + if (mConflictingQsExpansionGesture || mTwoFingerQsExpand) { return; } if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) { 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 f9aadf3199e3..04f9c7243df8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -61,7 +61,6 @@ import android.os.Message; import android.os.PowerManager; import android.os.RemoteException; import android.os.SystemClock; -import android.os.Trace; import android.os.UserHandle; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; @@ -73,6 +72,7 @@ import android.util.EventLog; import android.util.Log; import android.view.Display; import android.view.Gravity; +import android.view.HardwareCanvas; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -82,6 +82,7 @@ import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.ViewPropertyAnimator; import android.view.ViewStub; +import android.view.ViewTreeObserver; import android.view.WindowManager; import android.view.animation.AccelerateInterpolator; import android.view.animation.Animation; @@ -119,7 +120,6 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.BluetoothControllerImpl; import com.android.systemui.statusbar.policy.CastControllerImpl; -import com.android.systemui.statusbar.policy.DateView; import com.android.systemui.statusbar.policy.FlashlightController; import com.android.systemui.statusbar.policy.HeadsUpNotificationView; import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; @@ -130,6 +130,7 @@ import com.android.systemui.statusbar.policy.RotationLockControllerImpl; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout.OnChildLocationsChangedListener; +import com.android.systemui.statusbar.stack.StackScrollAlgorithm; import com.android.systemui.statusbar.stack.StackScrollState.ViewState; import com.android.systemui.volume.VolumeComponent; @@ -383,6 +384,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, private final ShadeUpdates mShadeUpdates = new ShadeUpdates(); + private int mDrawCount; + private static final int VISIBLE_LOCATIONS = ViewState.LOCATION_FIRST_CARD | ViewState.LOCATION_TOP_STACK_PEEKING | ViewState.LOCATION_MAIN_AREA @@ -723,9 +726,34 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, // listen for USER_SETUP_COMPLETE setting (per-user) resetUserSetupObserver(); + startGlyphRasterizeHack(); return mStatusBarView; } + /** + * Hack to improve glyph rasterization for scaled text views. + */ + private void startGlyphRasterizeHack() { + mStatusBarView.getViewTreeObserver().addOnPreDrawListener( + new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + if (mDrawCount == 1) { + mStatusBarView.getViewTreeObserver().removeOnPreDrawListener(this); + HardwareCanvas.setProperty("extraRasterBucket", + Float.toString(StackScrollAlgorithm.DIMMED_SCALE)); + HardwareCanvas.setProperty("extraRasterBucket", Float.toString( + mContext.getResources().getDimensionPixelSize( + R.dimen.qs_time_collapsed_size) + / mContext.getResources().getDimensionPixelSize( + R.dimen.qs_time_expanded_size))); + } + mDrawCount++; + return true; + } + }); + } + @Override protected void setZenMode(int mode) { super.setZenMode(mode); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java index fa8e1a6b9243..1bb36dd796d7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.phone; +import android.app.AlarmManager; import android.app.StatusBarManager; import android.bluetooth.BluetoothAdapter; import android.content.BroadcastReceiver; @@ -24,6 +25,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.media.AudioManager; import android.os.Handler; +import android.os.UserHandle; import android.provider.Settings.Global; import android.util.Log; @@ -71,8 +73,8 @@ public class PhoneStatusBarPolicy { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - if (action.equals(Intent.ACTION_ALARM_CHANGED)) { - updateAlarm(intent); + if (action.equals(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)) { + updateAlarm(); } else if (action.equals(Intent.ACTION_SYNC_STATE_CHANGED)) { updateSyncState(intent); @@ -90,6 +92,9 @@ public class PhoneStatusBarPolicy { else if (action.equals(TtyIntent.TTY_ENABLED_CHANGE_ACTION)) { updateTTY(intent); } + else if (action.equals(Intent.ACTION_USER_SWITCHED)) { + updateAlarm(); + } } }; @@ -99,13 +104,14 @@ public class PhoneStatusBarPolicy { // listen for broadcasts IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_ALARM_CHANGED); + filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED); filter.addAction(Intent.ACTION_SYNC_STATE_CHANGED); filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED); filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED); filter.addAction(TtyIntent.TTY_ENABLED_CHANGE_ACTION); + filter.addAction(Intent.ACTION_USER_SWITCHED); mContext.registerReceiver(mIntentReceiver, filter, null, mHandler); // TTY status @@ -152,8 +158,9 @@ public class PhoneStatusBarPolicy { updateVolumeZen(); } - private final void updateAlarm(Intent intent) { - boolean alarmSet = intent.getBooleanExtra("alarmSet", false); + private void updateAlarm() { + AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + boolean alarmSet = alarmManager.getNextAlarmClock(UserHandle.USER_CURRENT) != null; mService.setIconVisibility(SLOT_ALARM_CLOCK, alarmSet); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java index b95b88f50755..0fbdeeb4dc2c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java @@ -46,6 +46,7 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL private boolean mListening; private boolean mOverscrolled; private boolean mKeyguardShowing; + private boolean mCharging; private ViewGroup mSystemIconsContainer; private View mSystemIconsSuperContainer; @@ -80,6 +81,7 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL private int mClockMarginBottomExpanded; private int mMultiUserSwitchWidthCollapsed; private int mMultiUserSwitchWidthExpanded; + private int mBatteryPaddingEnd; /** * In collapsed QS, the clock and avatar are scaled down a bit post-layout to allow for a nice @@ -164,6 +166,8 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL mClockCollapsedScaleFactor = getResources().getDimensionPixelSize(R.dimen.qs_time_collapsed_size) / mClock.getTextSize(); + mBatteryPaddingEnd = + getResources().getDimensionPixelSize(R.dimen.battery_level_padding_end); } public void setActivityStarter(ActivityStarter activityStarter) { @@ -210,6 +214,7 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL updateClockScale(); updateAvatarScale(); updateClockLp(); + updateBatteryLevelPaddingEnd(); } } @@ -273,12 +278,13 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL ? VISIBLE : GONE); mMultiUserSwitch.setVisibility(mExpanded || !mKeyguardUserSwitcherShowing ? VISIBLE : GONE); - mBatteryLevel.setVisibility(mExpanded && !mOverscrolled ? View.VISIBLE : View.GONE); + mBatteryLevel.setVisibility(mKeyguardShowing && mCharging || mExpanded && !mOverscrolled + ? View.VISIBLE : View.GONE); } private void updateSystemIconsLayoutParams() { RelativeLayout.LayoutParams lp = (LayoutParams) mSystemIconsSuperContainer.getLayoutParams(); - lp.addRule(RelativeLayout.START_OF, mExpanded + lp.addRule(RelativeLayout.START_OF, mExpanded && !mOverscrolled ? mSettingsButton.getId() : mMultiUserSwitch.getId()); lp.removeRule(ALIGN_PARENT_START); @@ -318,9 +324,19 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL } } + private void updateBatteryLevelPaddingEnd() { + mBatteryLevel.setPaddingRelative(0, 0, + mKeyguardShowing && !mExpanded ? 0 : mBatteryPaddingEnd, 0); + } + @Override public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) { mBatteryLevel.setText(getResources().getString(R.string.battery_level_template, level)); + boolean changed = mCharging != charging; + mCharging = charging; + if (changed) { + updateVisibilities(); + } } private void updateClickTargets() { @@ -361,14 +377,16 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL private void updateMultiUserSwitch() { int marginEnd; - if (mExpanded) { + if (mExpanded && !mOverscrolled) { marginEnd = mMultiUserExpandedMargin; } else if (mKeyguardShowing) { marginEnd = mMultiUserKeyguardMargin; } else { marginEnd = mMultiUserCollapsedMargin; } - int width = mExpanded ? mMultiUserSwitchWidthExpanded : mMultiUserSwitchWidthCollapsed; + int width = mExpanded && !mOverscrolled + ? mMultiUserSwitchWidthExpanded + : mMultiUserSwitchWidthCollapsed; MarginLayoutParams lp = (MarginLayoutParams) mMultiUserSwitch.getLayoutParams(); if (marginEnd != lp.getMarginEnd() || lp.width != width) { lp.setMarginEnd(marginEnd); @@ -421,6 +439,7 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL updatePadding(); updateMultiUserSwitch(); updateClickTargets(); + updateBatteryLevelPaddingEnd(); } public void setUserInfoController(UserInfoController userInfoController) { @@ -484,6 +503,12 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL return !mKeyguardShowing || mExpanded; } + @Override + protected void dispatchSetPressed(boolean pressed) { + // We don't want that everything lights up when we click on the header, so block the request + // here. + } + private final QSPanel.Callback mQsPanelCallback = new QSPanel.Callback() { @Override public void onToggleStateChanged(final boolean state) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index 799b41fd6360..2b089022b0ae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -34,6 +34,7 @@ import android.os.Message; import android.os.Messenger; import android.provider.Settings; import android.telephony.PhoneStateListener; +import android.telephony.Rlog; import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.TelephonyManager; @@ -474,8 +475,8 @@ public class NetworkControllerImpl extends BroadcastReceiver PhoneStateListener mPhoneStateListener = new PhoneStateListener() { @Override public void onSignalStrengthsChanged(SignalStrength signalStrength) { - if (DEBUG) { - Log.d(TAG, "onSignalStrengthsChanged signalStrength=" + signalStrength + + if (true/*DEBUG*/) { + Rlog.d(TAG, "onSignalStrengthsChanged signalStrength=" + signalStrength + ((signalStrength == null) ? "" : (" level=" + signalStrength.getLevel()))); } mSignalStrength = signalStrength; @@ -485,8 +486,8 @@ public class NetworkControllerImpl extends BroadcastReceiver @Override public void onServiceStateChanged(ServiceState state) { - if (DEBUG) { - Log.d(TAG, "onServiceStateChanged voiceState=" + state.getVoiceRegState() + if (true/*DEBUG*/) { + Rlog.d(TAG, "onServiceStateChanged voiceState=" + state.getVoiceRegState() + " dataState=" + state.getDataRegState()); } mServiceState = state; @@ -498,8 +499,8 @@ public class NetworkControllerImpl extends BroadcastReceiver @Override public void onCallStateChanged(int state, String incomingNumber) { - if (DEBUG) { - Log.d(TAG, "onCallStateChanged state=" + state); + if (true/*DEBUG*/) { + Rlog.d(TAG, "onCallStateChanged state=" + state); } // In cdma, if a voice call is made, RSSI should switch to 1x. if (isCdma()) { @@ -510,8 +511,8 @@ public class NetworkControllerImpl extends BroadcastReceiver @Override public void onDataConnectionStateChanged(int state, int networkType) { - if (DEBUG) { - Log.d(TAG, "onDataConnectionStateChanged: state=" + state + if (true/*DEBUG*/) { + Rlog.d(TAG, "onDataConnectionStateChanged: state=" + state + " type=" + networkType); } mDataState = state; @@ -523,8 +524,8 @@ public class NetworkControllerImpl extends BroadcastReceiver @Override public void onDataActivity(int direction) { - if (DEBUG) { - Log.d(TAG, "onDataActivity: direction=" + direction); + if (true/*DEBUG*/) { + Rlog.d(TAG, "onDataActivity: direction=" + direction); } mDataActivity = direction; updateDataIcon(); @@ -555,6 +556,7 @@ public class NetworkControllerImpl extends BroadcastReceiver } else { mSimState = IccCardConstants.State.UNKNOWN; } + Rlog.d(TAG, "updateSimState: mSimState=" + mSimState); } private boolean isCdma() { @@ -562,6 +564,7 @@ public class NetworkControllerImpl extends BroadcastReceiver } private boolean hasService() { + boolean retVal; if (mServiceState != null) { // Consider the device to be in service if either voice or data service is available. // Some SIM cards are marketed as data-only and do not support voice service, and on @@ -569,16 +572,18 @@ public class NetworkControllerImpl extends BroadcastReceiver // service" or "emergency calls only" text that indicates that voice is not available. switch(mServiceState.getVoiceRegState()) { case ServiceState.STATE_POWER_OFF: - return false; + retVal = false; case ServiceState.STATE_OUT_OF_SERVICE: case ServiceState.STATE_EMERGENCY_ONLY: - return mServiceState.getDataRegState() == ServiceState.STATE_IN_SERVICE; + retVal = mServiceState.getDataRegState() == ServiceState.STATE_IN_SERVICE; default: - return true; + retVal = true; } } else { - return false; + retVal = false; } + Rlog.d(TAG, "hasService: mServiceState=" + mServiceState + " retVal=" + retVal); + return retVal; } private void updateAirplaneMode() { @@ -591,14 +596,15 @@ public class NetworkControllerImpl extends BroadcastReceiver } private final void updateTelephonySignalStrength() { - if (!hasService()) { + Rlog.d(TAG, "updateTelephonySignalStrength: hasService=" + hasService() + " ss=" + mSignalStrength); + if (false/*!hasService()*/) { if (CHATTY) Log.d(TAG, "updateTelephonySignalStrength: !hasService()"); mPhoneSignalIconId = R.drawable.stat_sys_signal_null; mQSPhoneSignalIconId = R.drawable.ic_qs_signal_no_signal; mDataSignalIconId = R.drawable.stat_sys_signal_null; } else { if (mSignalStrength == null) { - if (CHATTY) Log.d(TAG, "updateTelephonySignalStrength: mSignalStrength == null"); + if (true/*CHATTY*/) Rlog.d(TAG, "updateTelephonySignalStrength: mSignalStrength == null"); mPhoneSignalIconId = R.drawable.stat_sys_signal_null; mQSPhoneSignalIconId = R.drawable.ic_qs_signal_no_signal; mDataSignalIconId = R.drawable.stat_sys_signal_null; @@ -609,7 +615,7 @@ public class NetworkControllerImpl extends BroadcastReceiver int[] iconList; if (isCdma() && mAlwaysShowCdmaRssi) { mLastSignalLevel = iconLevel = mSignalStrength.getCdmaLevel(); - if(DEBUG) Log.d(TAG, "mAlwaysShowCdmaRssi=" + mAlwaysShowCdmaRssi + if(true/*DEBUG*/) Rlog.d(TAG, "updateTelephonySignalStrength: mAlwaysShowCdmaRssi=" + mAlwaysShowCdmaRssi + " set to cdmaLevel=" + mSignalStrength.getCdmaLevel() + " instead of level=" + mSignalStrength.getLevel()); } else { @@ -636,6 +642,7 @@ public class NetworkControllerImpl extends BroadcastReceiver mContentDescriptionPhoneSignal = mContext.getString( AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[iconLevel]); mDataSignalIconId = TelephonyIcons.DATA_SIGNAL_STRENGTH[mInetCondition][iconLevel]; + Rlog.d(TAG, "updateTelephonySignalStrength: iconLevel=" + iconLevel); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java index c22b8c210906..ee7206f86128 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java @@ -41,7 +41,7 @@ public class StackScrollAlgorithm { /** When a child is activated, the other cards' alpha fade to this value. */ private static final float ACTIVATED_INVERSE_ALPHA = 0.9f; - private static final float DIMMED_SCALE = 0.95f; + public static final float DIMMED_SCALE = 0.95f; private int mPaddingBetweenElements; private int mCollapsedSize; diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java index 8532810bfb71..9bc00a95005f 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java +++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java @@ -85,7 +85,6 @@ public class ZenModePanel extends LinearLayout { private TextView mZenSubheadExpanded; private View mMoreSettings; private LinearLayout mZenConditions; - private View mAlarmWarning; private Callback mCallback; private ZenModeController mController; @@ -152,8 +151,6 @@ public class ZenModePanel extends LinearLayout { }); mZenConditions = (LinearLayout) findViewById(R.id.zen_conditions); - - mAlarmWarning = findViewById(R.id.zen_alarm_warning); } @Override @@ -279,10 +276,9 @@ public class ZenModePanel extends LinearLayout { mZenSubheadCollapsed.setVisibility(!mExpanded ? VISIBLE : GONE); mMoreSettings.setVisibility(zenImportant && mExpanded ? VISIBLE : GONE); mZenConditions.setVisibility(!zenOff && mExpanded ? VISIBLE : GONE); - mAlarmWarning.setVisibility(zenNone && mExpanded ? VISIBLE : GONE); if (zenNone) { - mZenSubheadExpanded.setText(R.string.zen_no_interruptions); + mZenSubheadExpanded.setText(R.string.zen_no_interruptions_with_warning); mZenSubheadCollapsed.setText(mExitConditionText); } else if (zenImportant) { mZenSubheadExpanded.setText(R.string.zen_important_interruptions); diff --git a/phone/java/android/phone/PhoneManager.java b/phone/java/android/phone/PhoneManager.java index 360565e0634c..f61b0544c285 100644 --- a/phone/java/android/phone/PhoneManager.java +++ b/phone/java/android/phone/PhoneManager.java @@ -30,20 +30,17 @@ public final class PhoneManager { private static final String TAG = PhoneManager.class.getSimpleName(); private final Context mContext; - private final ITelecommService mService; /** * @hide */ - public PhoneManager(Context context, ITelecommService service) { + public PhoneManager(Context context) { Context appContext = context.getApplicationContext(); if (appContext != null) { mContext = appContext; } else { mContext = context; } - - mService = service; } /** @@ -56,10 +53,13 @@ public final class PhoneManager { * @return True if the digits were processed as an MMI code, false otherwise. */ public boolean handlePinMmi(String dialString) { - try { - return mService.handlePinMmi(dialString); - } catch (RemoteException e) { - Log.e(TAG, "Error calling ITelecommService#handlePinMmi", e); + ITelecommService service = getTelecommService(); + if (service != null) { + try { + return service.handlePinMmi(dialString); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecommService#handlePinMmi", e); + } } return false; } @@ -71,10 +71,13 @@ public final class PhoneManager { * </p> */ public void cancelMissedCallsNotification() { - try { - mService.cancelMissedCallsNotification(); - } catch (RemoteException e) { - Log.e(TAG, "Error calling ITelecommService#cancelMissedCallNotification", e); + ITelecommService service = getTelecommService(); + if (service != null) { + try { + service.cancelMissedCallsNotification(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecommService#cancelMissedCallsNotification", e); + } } } @@ -89,10 +92,13 @@ public final class PhoneManager { * @param showDialpad Brings up the in-call dialpad as part of showing the in-call screen. */ public void showCallScreen(boolean showDialpad) { - try { - mService.showCallScreen(showDialpad); - } catch (RemoteException e) { - Log.e(TAG, "Error calling ITelecommService#showCallScreen", e); + ITelecommService service = getTelecommService(); + if (service != null) { + try { + service.showCallScreen(showDialpad); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecommService#showCallScreen", e); + } } } @@ -103,11 +109,19 @@ public final class PhoneManager { * </p> */ public boolean isInAPhoneCall() { - try { - return mService.isInAPhoneCall(); - } catch (RemoteException e) { - Log.e(TAG, "Error caling ITelecommService#isInAPhoneCall", e); + ITelecommService service = getTelecommService(); + if (service != null) { + try { + return service.isInAPhoneCall(); + } catch (RemoteException e) { + Log.e(TAG, "Error caling ITelecommService#isInAPhoneCall", e); + } } return false; } + + private ITelecommService getTelecommService() { + return ITelecommService.Stub.asInterface( + ServiceManager.getService(Context.TELECOMM_SERVICE)); + } } diff --git a/policy/src/com/android/internal/policy/impl/PhoneFallbackEventHandler.java b/policy/src/com/android/internal/policy/impl/PhoneFallbackEventHandler.java index 4ad45a8e234e..f291e89f3154 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneFallbackEventHandler.java +++ b/policy/src/com/android/internal/policy/impl/PhoneFallbackEventHandler.java @@ -83,7 +83,7 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler { case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_DOWN: case KeyEvent.KEYCODE_VOLUME_MUTE: { - getAudioManager().handleKeyDown(event, AudioManager.USE_DEFAULT_STREAM_TYPE); + MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(event, false); return true; } @@ -198,11 +198,7 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler { case KeyEvent.KEYCODE_VOLUME_DOWN: case KeyEvent.KEYCODE_VOLUME_MUTE: { if (!event.isCanceled()) { - AudioManager audioManager = (AudioManager)mContext.getSystemService( - Context.AUDIO_SERVICE); - if (audioManager != null) { - getAudioManager().handleKeyUp(event, AudioManager.USE_DEFAULT_STREAM_TYPE); - } + MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(event, false); } return true; } diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index c668f5a6eea5..0b1252ea361e 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -45,7 +45,11 @@ import android.media.AudioManager; import android.media.IAudioService; import android.media.Ringtone; import android.media.RingtoneManager; +import android.media.session.MediaController; +import android.media.session.MediaSession; import android.media.session.MediaSessionLegacyHelper; +import android.media.session.MediaSessionManager; +import android.media.session.PlaybackState; import android.os.Bundle; import android.os.FactoryTest; import android.os.Handler; @@ -113,6 +117,7 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import static android.view.WindowManager.LayoutParams.*; import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT; @@ -4006,21 +4011,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { setHdmiPlugged(!mHdmiPlugged); } - /** - * @return Whether music is being played right now "locally" (e.g. on the device's speakers - * or wired headphones) or "remotely" (e.g. on a device using the Cast protocol and - * controlled by this device, or through remote submix). - */ - boolean isMusicActive() { - - final AudioManager am = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE); - if (am == null) { - Log.w(TAG, "isMusicActive: couldn't get AudioManager reference"); - return false; - } - return am.isLocalOrRemoteMusicActive(); - } - final Object mScreenshotLock = new Object(); ServiceConnection mScreenshotConnection = null; @@ -4210,7 +4200,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { // the application, just pass it to the session service. MediaSessionLegacyHelper.getHelper(mContext) - .sendMediaButtonEvent(event, true); + .sendVolumeKeyEvent(event, false); break; } } @@ -4220,7 +4210,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { // handled it send it to the session manager to figure // out. MediaSessionLegacyHelper.getHelper(mContext) - .sendMediaButtonEvent(event, true); + .sendVolumeKeyEvent(event, true); break; } } diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index 4ab8a011c995..21e56702b6ad 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -17,7 +17,9 @@ package com.android.server; import android.app.Activity; +import android.app.ActivityManager; import android.app.ActivityManagerNative; +import android.app.AlarmClockInfo; import android.app.AlarmManager; import android.app.IAlarmManager; import android.app.PendingIntent; @@ -37,10 +39,14 @@ import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; import android.os.WorkSource; +import android.provider.Settings; import android.text.TextUtils; +import android.text.format.DateFormat; import android.util.ArrayMap; +import android.util.Log; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.util.TimeUtils; import java.io.ByteArrayOutputStream; @@ -54,6 +60,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.LinkedList; +import java.util.Locale; import java.util.TimeZone; import static android.app.AlarmManager.RTC_WAKEUP; @@ -83,15 +90,19 @@ class AlarmManagerService extends SystemService { static final boolean localLOGV = false; static final boolean DEBUG_BATCH = localLOGV || false; static final boolean DEBUG_VALIDATE = localLOGV || false; + static final boolean DEBUG_ALARM_CLOCK = localLOGV || false; static final int ALARM_EVENT = 1; static final String TIMEZONE_PROPERTY = "persist.sys.timezone"; - + static final Intent mBackgroundIntent = new Intent().addFlags(Intent.FLAG_FROM_BACKGROUND); static final IncreasingTimeOrder sIncreasingTimeOrder = new IncreasingTimeOrder(); static final boolean WAKEUP_STATS = false; + private static final Intent NEXT_ALARM_CLOCK_CHANGED_INTENT = new Intent( + AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED); + final LocalLog mLog = new LocalLog(TAG); final Object mLock = new Object(); @@ -118,6 +129,15 @@ class AlarmManagerService extends SystemService { long mStartCurrentDelayTime; long mNextNonWakeupDeliveryTime; + private final SparseArray<AlarmClockInfo> mNextAlarmClockForUser = new SparseArray<>(); + private final SparseArray<AlarmClockInfo> mTmpSparseAlarmClockArray = new SparseArray<>(); + private final SparseBooleanArray mPendingSendNextAlarmClockChangedForUser = + new SparseBooleanArray(); + private boolean mNextAlarmClockMayChange; + + // May only use on mHandler's thread, locking not required. + private final SparseArray<AlarmClockInfo> mHandlerSparseAlarmClockArray = new SparseArray<>(); + class WakeupEvent { public long when; public int uid; @@ -133,7 +153,7 @@ class AlarmManagerService extends SystemService { final LinkedList<WakeupEvent> mRecentWakeups = new LinkedList<WakeupEvent>(); final long RECENT_WAKEUP_PERIOD = 1000L * 60 * 60 * 24; // one day - static final class Batch { + final class Batch { long start; // These endpoints are always in ELAPSED long end; boolean standalone; // certain "batches" don't participate in coalescing @@ -197,6 +217,9 @@ class AlarmManagerService extends SystemService { if (alarm.operation.equals(operation)) { alarms.remove(i); didRemove = true; + if (alarm.alarmClock != null) { + mNextAlarmClockMayChange = true; + } } else { if (alarm.whenElapsed > newStart) { newStart = alarm.whenElapsed; @@ -224,6 +247,9 @@ class AlarmManagerService extends SystemService { if (alarm.operation.getTargetPackage().equals(packageName)) { alarms.remove(i); didRemove = true; + if (alarm.alarmClock != null) { + mNextAlarmClockMayChange = true; + } } else { if (alarm.whenElapsed > newStart) { newStart = alarm.whenElapsed; @@ -251,6 +277,9 @@ class AlarmManagerService extends SystemService { if (UserHandle.getUserId(alarm.operation.getCreatorUid()) == userHandle) { alarms.remove(i); didRemove = true; + if (alarm.alarmClock != null) { + mNextAlarmClockMayChange = true; + } } else { if (alarm.whenElapsed > newStart) { newStart = alarm.whenElapsed; @@ -426,7 +455,8 @@ class AlarmManagerService extends SystemService { : maxTriggerTime(nowElapsed, whenElapsed, a.repeatInterval); } setImplLocked(a.type, a.when, whenElapsed, a.windowLength, maxElapsed, - a.repeatInterval, a.operation, batch.standalone, doValidate, a.workSource); + a.repeatInterval, a.operation, batch.standalone, doValidate, a.workSource, + a.alarmClock, a.userId); } } } @@ -588,7 +618,8 @@ class AlarmManagerService extends SystemService { } void setImpl(int type, long triggerAtTime, long windowLength, long interval, - PendingIntent operation, boolean isStandalone, WorkSource workSource) { + PendingIntent operation, boolean isStandalone, WorkSource workSource, + AlarmClockInfo alarmClock) { if (operation == null) { Slog.w(TAG, "set/setRepeating ignored because there is no intent"); return; @@ -625,6 +656,8 @@ class AlarmManagerService extends SystemService { maxElapsed = triggerElapsed + windowLength; } + final int userId = UserHandle.getCallingUserId(); + synchronized (mLock) { if (DEBUG_BATCH) { Slog.v(TAG, "set(" + operation + ") : type=" + type @@ -633,15 +666,15 @@ class AlarmManagerService extends SystemService { + " interval=" + interval + " standalone=" + isStandalone); } setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, maxElapsed, - interval, operation, isStandalone, true, workSource); + interval, operation, isStandalone, true, workSource, alarmClock, userId); } } private void setImplLocked(int type, long when, long whenElapsed, long windowLength, long maxWhen, long interval, PendingIntent operation, boolean isStandalone, - boolean doValidate, WorkSource workSource) { + boolean doValidate, WorkSource workSource, AlarmClockInfo alarmClock, int userId) { Alarm a = new Alarm(type, when, whenElapsed, windowLength, maxWhen, interval, - operation, workSource); + operation, workSource, alarmClock, userId); removeLocked(operation); int whichBatch = (isStandalone) ? -1 : attemptCoalesceLocked(whenElapsed, maxWhen); @@ -659,6 +692,11 @@ class AlarmManagerService extends SystemService { } } + if (alarmClock != null) { + mNextAlarmClockMayChange = true; + updateNextAlarmClockLocked(); + } + if (DEBUG_VALIDATE) { if (doValidate && !validateConsistencyLocked()) { Slog.v(TAG, "Tipping-point operation: type=" + type + " when=" + when @@ -676,7 +714,7 @@ class AlarmManagerService extends SystemService { private final IBinder mService = new IAlarmManager.Stub() { @Override public void set(int type, long triggerAtTime, long windowLength, long interval, - PendingIntent operation, WorkSource workSource) { + PendingIntent operation, WorkSource workSource, AlarmClockInfo alarmClock) { if (workSource != null) { getContext().enforceCallingPermission( android.Manifest.permission.UPDATE_DEVICE_STATS, @@ -684,7 +722,7 @@ class AlarmManagerService extends SystemService { } setImpl(type, triggerAtTime, windowLength, interval, operation, - false, workSource); + false, workSource, alarmClock); } @Override @@ -724,6 +762,15 @@ class AlarmManagerService extends SystemService { } @Override + public AlarmClockInfo getNextAlarmClock(int userId) { + userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), + Binder.getCallingUid(), userId, false /* allowAll */, false /* requireFull */, + "getNextAlarmClock", null); + + return getNextAlarmClockImpl(userId); + } + + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { @@ -962,6 +1009,133 @@ class AlarmManagerService extends SystemService { return null; } + private AlarmClockInfo getNextAlarmClockImpl(int userId) { + synchronized (mLock) { + return mNextAlarmClockForUser.get(userId); + } + } + + /** + * Recomputes the next alarm clock for all users. + */ + private void updateNextAlarmClockLocked() { + if (!mNextAlarmClockMayChange) { + return; + } + mNextAlarmClockMayChange = false; + + SparseArray<AlarmClockInfo> nextForUser = mTmpSparseAlarmClockArray; + nextForUser.clear(); + + final int N = mAlarmBatches.size(); + for (int i = 0; i < N; i++) { + ArrayList<Alarm> alarms = mAlarmBatches.get(i).alarms; + final int M = alarms.size(); + + for (int j = 0; j < M; j++) { + Alarm a = alarms.get(j); + if (a.alarmClock != null) { + final int userId = a.userId; + + if (DEBUG_ALARM_CLOCK) { + Log.v(TAG, "Found AlarmClockInfo at " + + formatNextAlarm(getContext(), a.alarmClock) + + " for user " + userId); + } + + // Alarms and batches are sorted by time, no need to compare times here. + if (nextForUser.get(userId) == null) { + nextForUser.put(userId, a.alarmClock); + } + } + } + } + + // Update mNextAlarmForUser with new values. + final int NN = nextForUser.size(); + for (int i = 0; i < NN; i++) { + AlarmClockInfo newAlarm = nextForUser.valueAt(i); + int userId = nextForUser.keyAt(i); + AlarmClockInfo currentAlarm = mNextAlarmClockForUser.get(userId); + if (!newAlarm.equals(currentAlarm)) { + updateNextAlarmInfoForUserLocked(userId, newAlarm); + } + } + + // Remove users without any alarm clocks scheduled. + final int NNN = mNextAlarmClockForUser.size(); + for (int i = NNN - 1; i >= 0; i--) { + int userId = mNextAlarmClockForUser.keyAt(i); + if (nextForUser.get(userId) == null) { + updateNextAlarmInfoForUserLocked(userId, null); + } + } + } + + private void updateNextAlarmInfoForUserLocked(int userId, AlarmClockInfo alarmClock) { + if (alarmClock != null) { + if (DEBUG_ALARM_CLOCK) { + Log.v(TAG, "Next AlarmClockInfoForUser(" + userId + "): " + + formatNextAlarm(getContext(), alarmClock)); + } + mNextAlarmClockForUser.put(userId, alarmClock); + } else { + if (DEBUG_ALARM_CLOCK) { + Log.v(TAG, "Next AlarmClockInfoForUser(" + userId + "): None"); + } + mNextAlarmClockForUser.remove(userId); + } + + mPendingSendNextAlarmClockChangedForUser.put(userId, true); + mHandler.removeMessages(AlarmHandler.SEND_NEXT_ALARM_CLOCK_CHANGED); + mHandler.sendEmptyMessage(AlarmHandler.SEND_NEXT_ALARM_CLOCK_CHANGED); + } + + /** + * Updates NEXT_ALARM_FORMATTED and sends NEXT_ALARM_CLOCK_CHANGED_INTENT for all users + * for which alarm clocks have changed since the last call to this. + * + * Do not call with a lock held. Only call from mHandler's thread. + * + * @see AlarmHandler#SEND_NEXT_ALARM_CLOCK_CHANGED + */ + private void sendNextAlarmClockChanged() { + SparseArray<AlarmClockInfo> pendingUsers = mHandlerSparseAlarmClockArray; + pendingUsers.clear(); + + synchronized (mLock) { + final int N = mPendingSendNextAlarmClockChangedForUser.size(); + for (int i = 0; i < N; i++) { + int userId = mPendingSendNextAlarmClockChangedForUser.keyAt(i); + pendingUsers.append(userId, mNextAlarmClockForUser.get(userId)); + } + mPendingSendNextAlarmClockChangedForUser.clear(); + } + + final int N = pendingUsers.size(); + for (int i = 0; i < N; i++) { + int userId = pendingUsers.keyAt(i); + AlarmClockInfo alarmClock = pendingUsers.valueAt(i); + Settings.System.putStringForUser(getContext().getContentResolver(), + Settings.System.NEXT_ALARM_FORMATTED, + formatNextAlarm(getContext(), alarmClock), + userId); + + getContext().sendBroadcastAsUser(NEXT_ALARM_CLOCK_CHANGED_INTENT, + new UserHandle(userId)); + } + } + + /** + * Formats an alarm like platform/packages/apps/DeskClock used to. + */ + private static String formatNextAlarm(final Context context, AlarmClockInfo info) { + String skeleton = DateFormat.is24HourFormat(context) ? "EHm" : "Ehma"; + String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton); + return (info == null) ? "" : + DateFormat.format(pattern, info.getTriggerTime()).toString(); + } + void rescheduleKernelAlarmsLocked() { // Schedule the next upcoming wakeup alarm. If there is a deliverable batch // prior to that which contains no wakeups, we schedule that as well. @@ -1004,6 +1178,7 @@ class AlarmManagerService extends SystemService { } rebatchAllAlarmsLocked(true); rescheduleKernelAlarmsLocked(); + updateNextAlarmClockLocked(); } } @@ -1023,6 +1198,7 @@ class AlarmManagerService extends SystemService { } rebatchAllAlarmsLocked(true); rescheduleKernelAlarmsLocked(); + updateNextAlarmClockLocked(); } } @@ -1042,6 +1218,7 @@ class AlarmManagerService extends SystemService { } rebatchAllAlarmsLocked(true); rescheduleKernelAlarmsLocked(); + updateNextAlarmClockLocked(); } } @@ -1180,7 +1357,7 @@ class AlarmManagerService extends SystemService { setImplLocked(alarm.type, alarm.when + delta, nextElapsed, alarm.windowLength, maxTriggerTime(nowELAPSED, nextElapsed, alarm.repeatInterval), alarm.repeatInterval, alarm.operation, batch.standalone, true, - alarm.workSource); + alarm.workSource, alarm.alarmClock, alarm.userId); // For now we count this as a wakeup alarm, meaning it needs to be // delivered immediately. In the future we should change this, but @@ -1189,6 +1366,11 @@ class AlarmManagerService extends SystemService { } else if (alarm.wakeup) { hasWakeup = true; } + + // We removed an alarm clock. Let the caller recompute the next alarm clock. + if (alarm.alarmClock != null) { + mNextAlarmClockMayChange = true; + } } } @@ -1232,9 +1414,12 @@ class AlarmManagerService extends SystemService { public long whenElapsed; // 'when' in the elapsed time base public long maxWhen; // also in the elapsed time base public long repeatInterval; + public final AlarmClockInfo alarmClock; + public final int userId; public Alarm(int _type, long _when, long _whenElapsed, long _windowLength, long _maxWhen, - long _interval, PendingIntent _op, WorkSource _ws) { + long _interval, PendingIntent _op, WorkSource _ws, AlarmClockInfo _info, + int _userId) { type = _type; wakeup = _type == AlarmManager.ELAPSED_REALTIME_WAKEUP || _type == AlarmManager.RTC_WAKEUP; @@ -1246,6 +1431,8 @@ class AlarmManagerService extends SystemService { operation = _op; tag = makeTag(_op, _type); workSource = _ws; + alarmClock = _info; + userId = _userId; } public static String makeTag(PendingIntent pi, int type) { @@ -1468,12 +1655,14 @@ class AlarmManagerService extends SystemService { mPendingNonWakeupAlarms.addAll(triggerList); mNumDelayedAlarms += triggerList.size(); rescheduleKernelAlarmsLocked(); + updateNextAlarmClockLocked(); } else { // now deliver the alarm intents; if there are pending non-wakeup // alarms, we need to merge them in to the list. note we don't // just deliver them first because we generally want non-wakeup // alarms delivered after wakeup alarms. rescheduleKernelAlarmsLocked(); + updateNextAlarmClockLocked(); if (mPendingNonWakeupAlarms.size() > 0) { triggerList.addAll(mPendingNonWakeupAlarms); Collections.sort(triggerList, mAlarmDispatchComparator); @@ -1529,6 +1718,7 @@ class AlarmManagerService extends SystemService { public static final int ALARM_EVENT = 1; public static final int MINUTE_CHANGE_EVENT = 2; public static final int DATE_CHANGE_EVENT = 3; + public static final int SEND_NEXT_ALARM_CLOCK_CHANGED = 4; public AlarmHandler() { } @@ -1540,8 +1730,9 @@ class AlarmManagerService extends SystemService { final long nowRTC = System.currentTimeMillis(); final long nowELAPSED = SystemClock.elapsedRealtime(); triggerAlarmsLocked(triggerList, nowELAPSED, nowRTC); + updateNextAlarmClockLocked(); } - + // now trigger the alarms without the lock held for (int i=0; i<triggerList.size(); i++) { Alarm alarm = triggerList.get(i); @@ -1555,6 +1746,8 @@ class AlarmManagerService extends SystemService { } } } + } else if (msg.what == SEND_NEXT_ALARM_CLOCK_CHANGED) { + sendNextAlarmClockChanged(); } } } @@ -1596,7 +1789,7 @@ class AlarmManagerService extends SystemService { final WorkSource workSource = null; // Let system take blame for time tick events. setImpl(ELAPSED_REALTIME, SystemClock.elapsedRealtime() + tickEventDelay, 0, - 0, mTimeTickSender, true, workSource); + 0, mTimeTickSender, true, workSource, null); } public void scheduleDateChangedEvent() { @@ -1609,7 +1802,8 @@ class AlarmManagerService extends SystemService { calendar.add(Calendar.DAY_OF_MONTH, 1); final WorkSource workSource = null; // Let system take blame for date change events. - setImpl(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender, true, workSource); + setImpl(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender, true, workSource, + null); } } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 1083c4ccc63d..ea05b98e3be6 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -87,6 +87,7 @@ import android.net.ProxyDataTracker; import android.net.ProxyInfo; import android.net.RouteInfo; import android.net.SamplingDataTracker; +import android.net.UidRange; import android.net.Uri; import android.net.wimax.WimaxManagerConstants; import android.os.AsyncTask; @@ -235,7 +236,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { @GuardedBy("mVpns") private final SparseArray<Vpn> mVpns = new SparseArray<Vpn>(); - private VpnCallback mVpnCallback = new VpnCallback(); private boolean mLockdownEnabled; private LockdownVpnTracker mLockdownTracker; @@ -363,8 +363,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { */ private static final int EVENT_SET_POLICY_DATA_ENABLE = 12; - private static final int EVENT_VPN_STATE_CHANGED = 13; - /** * Used internally to disable fail fast of mobile data */ @@ -1071,6 +1069,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { */ private NetworkInfo getFilteredNetworkInfo(int networkType, int uid) { NetworkInfo info = getNetworkInfoForType(networkType); + return getFilteredNetworkInfo(info, networkType, uid); + } + + private NetworkInfo getFilteredNetworkInfo(NetworkInfo info, int networkType, int uid) { if (isNetworkBlocked(networkType, uid)) { // network is blocked; clone and override state info = new NetworkInfo(info); @@ -1176,6 +1178,24 @@ public class ConnectivityService extends IConnectivityManager.Stub { } @Override + public NetworkInfo getNetworkInfoForNetwork(Network network) { + enforceAccessPermission(); + if (network == null) return null; + + final int uid = Binder.getCallingUid(); + NetworkAgentInfo nai = null; + synchronized (mNetworkForNetId) { + nai = mNetworkForNetId.get(network.netId); + } + if (nai == null) return null; + synchronized (nai) { + if (nai.networkInfo == null) return null; + + return getFilteredNetworkInfo(nai.networkInfo, nai.networkInfo.getType(), uid); + } + } + + @Override public NetworkInfo[] getAllNetworkInfo() { enforceAccessPermission(); final int uid = Binder.getCallingUid(); @@ -1192,6 +1212,18 @@ public class ConnectivityService extends IConnectivityManager.Stub { } @Override + public Network[] getAllNetworks() { + enforceAccessPermission(); + final ArrayList<Network> result = new ArrayList(); + synchronized (mNetworkForNetId) { + for (int i = 0; i < mNetworkForNetId.size(); i++) { + result.add(new Network(mNetworkForNetId.valueAt(i).network)); + } + } + return result.toArray(new Network[result.size()]); + } + + @Override public boolean isNetworkSupported(int networkType) { enforceAccessPermission(); return (isNetworkTypeValid(networkType) && (getNetworkInfoForType(networkType) != null)); @@ -1223,16 +1255,31 @@ public class ConnectivityService extends IConnectivityManager.Stub { @Override public LinkProperties getLinkProperties(Network network) { enforceAccessPermission(); - NetworkAgentInfo nai = mNetworkForNetId.get(network.netId); - if (nai != null) return new LinkProperties(nai.linkProperties); + NetworkAgentInfo nai = null; + synchronized (mNetworkForNetId) { + nai = mNetworkForNetId.get(network.netId); + } + + if (nai != null) { + synchronized (nai) { + return new LinkProperties(nai.linkProperties); + } + } return null; } @Override public NetworkCapabilities getNetworkCapabilities(Network network) { enforceAccessPermission(); - NetworkAgentInfo nai = mNetworkForNetId.get(network.netId); - if (nai != null) return new NetworkCapabilities(nai.networkCapabilities); + NetworkAgentInfo nai = null; + synchronized (mNetworkForNetId) { + nai = mNetworkForNetId.get(network.netId); + } + if (nai != null) { + synchronized (nai) { + return new NetworkCapabilities(nai.networkCapabilities); + } + } return null; } @@ -1777,8 +1824,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { } return false; } - - DetailedState netState = nai.networkInfo.getDetailedState(); + DetailedState netState; + synchronized (nai) { + netState = nai.networkInfo.getDetailedState(); + } if ((netState != DetailedState.CONNECTED && netState != DetailedState.CAPTIVE_PORTAL_CHECK)) { @@ -1792,9 +1841,13 @@ public class ConnectivityService extends IConnectivityManager.Stub { final int uid = Binder.getCallingUid(); final long token = Binder.clearCallingIdentity(); try { - LinkProperties lp = nai.linkProperties; - boolean ok = modifyRouteToAddress(lp, addr, ADD, TO_DEFAULT_TABLE, exempt, - nai.network.netId, uid); + LinkProperties lp = null; + int netId = INVALID_NET_ID; + synchronized (nai) { + lp = nai.linkProperties; + netId = nai.network.netId; + } + boolean ok = modifyRouteToAddress(lp, addr, ADD, TO_DEFAULT_TABLE, exempt, netId, uid); if (DBG) log("requestRouteToHostAddress ok=" + ok); return ok; } finally { @@ -3096,7 +3149,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { } else { if (VDBG) log("Update of Linkproperties for " + nai.name()); LinkProperties oldLp = nai.linkProperties; - nai.linkProperties = (LinkProperties)msg.obj; + synchronized (nai) { + nai.linkProperties = (LinkProperties)msg.obj; + } updateLinkProperties(nai, oldLp); } break; @@ -3121,6 +3176,30 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (score != null) updateNetworkScore(nai, score.intValue()); break; } + case NetworkAgent.EVENT_UID_RANGES_ADDED: { + NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo); + if (nai == null) { + loge("EVENT_UID_RANGES_ADDED from unknown NetworkAgent"); + break; + } + try { + mNetd.addVpnUidRanges(nai.network.netId, (UidRange[])msg.obj); + } catch (RemoteException e) { + } + break; + } + case NetworkAgent.EVENT_UID_RANGES_REMOVED: { + NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo); + if (nai == null) { + loge("EVENT_UID_RANGES_REMOVED from unknown NetworkAgent"); + break; + } + try { + mNetd.removeVpnUidRanges(nai.network.netId, (UidRange[])msg.obj); + } catch (RemoteException e) { + } + break; + } case NetworkMonitor.EVENT_NETWORK_VALIDATED: { NetworkAgentInfo nai = (NetworkAgentInfo)msg.obj; handleConnectionValidated(nai); @@ -3131,6 +3210,16 @@ public class ConnectivityService extends IConnectivityManager.Stub { handleLingerComplete(nai); break; } + case NetworkMonitor.EVENT_PROVISIONING_NOTIFICATION: { + NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo); + if (nai == null) { + loge("EVENT_PROVISIONING_NOTIFICATION from unknown NetworkMonitor"); + break; + } + setProvNotificationVisibleIntent(msg.arg1 != 0, nai.networkInfo.getType(), + nai.networkInfo.getExtraInfo(), (PendingIntent)msg.obj); + break; + } case NetworkStateTracker.EVENT_STATE_CHANGED: { info = (NetworkInfo) msg.obj; NetworkInfo.State state = info.getState(); @@ -3242,7 +3331,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { loge("Error connecting NetworkAgent"); NetworkAgentInfo nai = mNetworkAgentInfos.remove(msg.replyTo); if (nai != null) { - mNetworkForNetId.remove(nai.network.netId); + synchronized (mNetworkForNetId) { + mNetworkForNetId.remove(nai.network.netId); + } mLegacyTypeTracker.remove(nai); } } @@ -3275,7 +3366,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { mNetworkAgentInfos.remove(msg.replyTo); updateClat(null, nai.linkProperties, nai); mLegacyTypeTracker.remove(nai); - mNetworkForNetId.remove(nai.network.netId); + synchronized (mNetworkForNetId) { + mNetworkForNetId.remove(nai.network.netId); + } // Since we've lost the network, go through all the requests that // it was satisfying and see if any other factory can satisfy them. final ArrayList<NetworkAgentInfo> toActivate = new ArrayList<NetworkAgentInfo>(); @@ -3388,12 +3481,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (affectedNetwork != null) { // check if this network still has live requests - otherwise, tear down // TODO - probably push this to the NF/NA - boolean keep = false; - for (int i = 0; i < affectedNetwork.networkRequests.size(); i++) { + boolean keep = affectedNetwork.isVPN(); + for (int i = 0; i < affectedNetwork.networkRequests.size() && !keep; i++) { NetworkRequest r = affectedNetwork.networkRequests.valueAt(i); if (mNetworkRequests.get(r).isRequest) { keep = true; - break; } } if (keep == false) { @@ -3473,12 +3565,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { handleSetPolicyDataEnable(networkType, enabled); break; } - case EVENT_VPN_STATE_CHANGED: { - if (mLockdownTracker != null) { - mLockdownTracker.onVpnStateChanged((NetworkInfo) msg.obj); - } - break; - } case EVENT_ENABLE_FAIL_FAST_MOBILE_DATA: { int tag = mEnableFailFastMobileDataTag.get(); if (msg.arg1 == tag) { @@ -3613,6 +3699,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { return mTethering.getErroredIfaces(); } + public String[] getTetheredDhcpRanges() { + enforceConnectivityInternalPermission(); + return mTethering.getTetheredDhcpRanges(); + } + // if ro.tether.denied = true we default to no tethering // gservices could set the secure setting to 1 though to enable it on a build where it // had previously been turned off. @@ -3981,36 +4072,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { } /** - * Protect a socket from VPN routing rules. This method is used by - * VpnBuilder and not available in ConnectivityManager. Permissions - * are checked in Vpn class. - * @hide - */ - @Override - public boolean protectVpn(ParcelFileDescriptor socket) { - throwIfLockdownEnabled(); - try { - int type = mActiveDefaultNetwork; - int user = UserHandle.getUserId(Binder.getCallingUid()); - if (ConnectivityManager.isNetworkTypeValid(type) && mNetTrackers[type] != null) { - synchronized(mVpns) { - mVpns.get(user).protect(socket); - } - return true; - } - } catch (Exception e) { - // ignore - } finally { - try { - socket.close(); - } catch (Exception e) { - // ignore - } - } - return false; - } - - /** * Prepare for a VPN application. This method is used by VpnDialogs * and not available in ConnectivityManager. Permissions are checked * in Vpn class. @@ -4104,144 +4165,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } - /** - * Callback for VPN subsystem. Currently VPN is not adapted to the service - * through NetworkStateTracker since it works differently. For example, it - * needs to override DNS servers but never takes the default routes. It - * relies on another data network, and it could keep existing connections - * alive after reconnecting, switching between networks, or even resuming - * from deep sleep. Calls from applications should be done synchronously - * to avoid race conditions. As these are all hidden APIs, refactoring can - * be done whenever a better abstraction is developed. - */ - public class VpnCallback { - private VpnCallback() { - } - - public void onStateChanged(NetworkInfo info) { - mHandler.obtainMessage(EVENT_VPN_STATE_CHANGED, info).sendToTarget(); - } - - public void override(String iface, List<String> dnsServers, List<String> searchDomains) { - if (dnsServers == null) { - restore(); - return; - } - - // Convert DNS servers into addresses. - List<InetAddress> addresses = new ArrayList<InetAddress>(); - for (String address : dnsServers) { - // Double check the addresses and remove invalid ones. - try { - addresses.add(InetAddress.parseNumericAddress(address)); - } catch (Exception e) { - // ignore - } - } - if (addresses.isEmpty()) { - restore(); - return; - } - - // Concatenate search domains into a string. - StringBuilder buffer = new StringBuilder(); - if (searchDomains != null) { - for (String domain : searchDomains) { - buffer.append(domain).append(' '); - } - } - String domains = buffer.toString().trim(); - - // Apply DNS changes. - synchronized (mDnsLock) { - // TODO: Re-enable this when the netId of the VPN is known. - // updateDnsLocked("VPN", netId, addresses, domains); - } - - // Temporarily disable the default proxy (not global). - synchronized (mProxyLock) { - mDefaultProxyDisabled = true; - if (mGlobalProxy == null && mDefaultProxy != null) { - sendProxyBroadcast(null); - } - } - - // TODO: support proxy per network. - } - - public void restore() { - synchronized (mProxyLock) { - mDefaultProxyDisabled = false; - if (mGlobalProxy == null && mDefaultProxy != null) { - sendProxyBroadcast(mDefaultProxy); - } - } - } - - public void protect(ParcelFileDescriptor socket) { - try { - final int mark = mNetd.getMarkForProtect(); - NetworkUtils.markSocket(socket.getFd(), mark); - } catch (RemoteException e) { - } - } - - public void setRoutes(String interfaze, List<RouteInfo> routes) { - for (RouteInfo route : routes) { - try { - mNetd.setMarkedForwardingRoute(interfaze, route); - } catch (RemoteException e) { - } - } - } - - public void setMarkedForwarding(String interfaze) { - try { - mNetd.setMarkedForwarding(interfaze); - } catch (RemoteException e) { - } - } - - public void clearMarkedForwarding(String interfaze) { - try { - mNetd.clearMarkedForwarding(interfaze); - } catch (RemoteException e) { - } - } - - public void addUserForwarding(String interfaze, int uid, boolean forwardDns) { - int uidStart = uid * UserHandle.PER_USER_RANGE; - int uidEnd = uidStart + UserHandle.PER_USER_RANGE - 1; - addUidForwarding(interfaze, uidStart, uidEnd, forwardDns); - } - - public void clearUserForwarding(String interfaze, int uid, boolean forwardDns) { - int uidStart = uid * UserHandle.PER_USER_RANGE; - int uidEnd = uidStart + UserHandle.PER_USER_RANGE - 1; - clearUidForwarding(interfaze, uidStart, uidEnd, forwardDns); - } - - public void addUidForwarding(String interfaze, int uidStart, int uidEnd, - boolean forwardDns) { - // TODO: Re-enable this when the netId of the VPN is known. - // try { - // mNetd.setUidRangeRoute(netId, uidStart, uidEnd, forwardDns); - // } catch (RemoteException e) { - // } - - } - - public void clearUidForwarding(String interfaze, int uidStart, int uidEnd, - boolean forwardDns) { - // TODO: Re-enable this when the netId of the VPN is known. - // try { - // mNetd.clearUidRangeRoute(interfaze, uidStart, uidEnd); - // } catch (RemoteException e) { - // } - - } - } - @Override public boolean updateLockdownVpn() { if (Binder.getCallingUid() != Process.SYSTEM_UID) { @@ -5039,6 +4962,40 @@ public class ConnectivityService extends IConnectivityManager.Stub { log("setProvNotificationVisible: E visible=" + visible + " networkType=" + networkType + " extraInfo=" + extraInfo + " url=" + url); } + Intent intent = null; + PendingIntent pendingIntent = null; + if (visible) { + switch (networkType) { + case ConnectivityManager.TYPE_WIFI: + intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | + Intent.FLAG_ACTIVITY_NEW_TASK); + pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0); + break; + case ConnectivityManager.TYPE_MOBILE: + case ConnectivityManager.TYPE_MOBILE_HIPRI: + intent = new Intent(CONNECTED_TO_PROVISIONING_NETWORK_ACTION); + intent.putExtra("EXTRA_URL", url); + intent.setFlags(0); + pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); + break; + default: + intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | + Intent.FLAG_ACTIVITY_NEW_TASK); + pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0); + break; + } + } + setProvNotificationVisibleIntent(visible, networkType, extraInfo, pendingIntent); + } + + private void setProvNotificationVisibleIntent(boolean visible, int networkType, + String extraInfo, PendingIntent intent) { + if (DBG) { + log("setProvNotificationVisibleIntent: E visible=" + visible + " networkType=" + + networkType + " extraInfo=" + extraInfo); + } Resources r = Resources.getSystem(); NotificationManager notificationManager = (NotificationManager) mContext @@ -5048,7 +5005,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { CharSequence title; CharSequence details; int icon; - Intent intent; Notification notification = new Notification(); switch (networkType) { case ConnectivityManager.TYPE_WIFI: @@ -5056,10 +5012,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { details = r.getString(R.string.network_available_sign_in_detailed, extraInfo); icon = R.drawable.stat_notify_wifi_in_range; - intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | - Intent.FLAG_ACTIVITY_NEW_TASK); - notification.contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0); break; case ConnectivityManager.TYPE_MOBILE: case ConnectivityManager.TYPE_MOBILE_HIPRI: @@ -5068,20 +5020,12 @@ public class ConnectivityService extends IConnectivityManager.Stub { // name has been added to it details = mTelephonyManager.getNetworkOperatorName(); icon = R.drawable.stat_notify_rssi_in_range; - intent = new Intent(CONNECTED_TO_PROVISIONING_NETWORK_ACTION); - intent.putExtra("EXTRA_URL", url); - intent.setFlags(0); - notification.contentIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); break; default: title = r.getString(R.string.network_available_sign_in, 0); details = r.getString(R.string.network_available_sign_in_detailed, extraInfo); icon = R.drawable.stat_notify_rssi_in_range; - intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | - Intent.FLAG_ACTIVITY_NEW_TASK); - notification.contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0); break; } @@ -5090,6 +5034,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { notification.flags = Notification.FLAG_AUTO_CANCEL; notification.tickerText = title; notification.setLatestEventInfo(mContext, title, details, notification.contentIntent); + notification.contentIntent = intent; try { notificationManager.notify(NOTIFICATION_ID, networkType, notification); @@ -5263,9 +5208,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { loge("Starting user already has a VPN"); return; } - userVpn = new Vpn(mContext, mVpnCallback, mNetd, this, userId); + userVpn = new Vpn(mHandler.getLooper(), mContext, mNetd, this, userId); mVpns.put(userId, userVpn); - userVpn.startMonitoring(mContext, mTrackerHandler); } } @@ -5565,7 +5509,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { private void handleRegisterNetworkAgent(NetworkAgentInfo na) { if (VDBG) log("Got NetworkAgent Messenger"); mNetworkAgentInfos.put(na.messenger, na); - mNetworkForNetId.put(na.network.netId, na); + synchronized (mNetworkForNetId) { + mNetworkForNetId.put(na.network.netId, na); + } na.asyncChannel.connect(mContext, mTrackerHandler, na.messenger); NetworkInfo networkInfo = na.networkInfo; na.networkInfo = null; @@ -5710,7 +5656,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { NetworkCapabilities networkCapabilities) { // TODO - what else here? Verify still satisfies everybody? // Check if satisfies somebody new? call callbacks? - networkAgent.networkCapabilities = networkCapabilities; + synchronized (networkAgent) { + networkAgent.networkCapabilities = networkCapabilities; + } } private void sendUpdatedScoreToFactories(NetworkRequest networkRequest, int score) { @@ -5783,7 +5731,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { loge("Unknown NetworkAgentInfo in handleConnectionValidated"); return; } - boolean keep = false; + boolean keep = newNetwork.isVPN(); boolean isNewDefault = false; if (DBG) log("handleConnectionValidated for "+newNetwork.name()); // check if any NetworkRequest wants this NetworkAgent @@ -5845,8 +5793,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } for (NetworkAgentInfo nai : affectedNetworks) { - boolean teardown = true; - for (int i = 0; i < nai.networkRequests.size(); i++) { + boolean teardown = !nai.isVPN(); + for (int i = 0; i < nai.networkRequests.size() && teardown; i++) { NetworkRequest nr = nai.networkRequests.valueAt(i); try { if (mNetworkRequests.get(nr).isRequest) { @@ -5924,8 +5872,14 @@ public class ConnectivityService extends IConnectivityManager.Stub { private void updateNetworkInfo(NetworkAgentInfo networkAgent, NetworkInfo newInfo) { NetworkInfo.State state = newInfo.getState(); - NetworkInfo oldInfo = networkAgent.networkInfo; - networkAgent.networkInfo = newInfo; + NetworkInfo oldInfo = null; + synchronized (networkAgent) { + oldInfo = networkAgent.networkInfo; + networkAgent.networkInfo = newInfo; + } + if (networkAgent.isVPN() && mLockdownTracker != null) { + mLockdownTracker.onVpnStateChanged(newInfo); + } if (oldInfo != null && oldInfo.getState() == state) { if (VDBG) log("ignoring duplicate network state non-change"); @@ -5944,7 +5898,12 @@ public class ConnectivityService extends IConnectivityManager.Stub { // CONNECTING and back (like wifi on DHCP renew). // TODO: keep track of which networks we've created, or ask netd // to tell us whether we've already created this network or not. - mNetd.createNetwork(networkAgent.network.netId); + if (networkAgent.isVPN()) { + mNetd.createVirtualNetwork(networkAgent.network.netId, + !networkAgent.linkProperties.getDnsServers().isEmpty()); + } else { + mNetd.createPhysicalNetwork(networkAgent.network.netId); + } } catch (Exception e) { loge("Error creating network " + networkAgent.network.netId + ": " + e.getMessage()); @@ -5954,9 +5913,31 @@ public class ConnectivityService extends IConnectivityManager.Stub { updateLinkProperties(networkAgent, null); notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_PRECHECK); networkAgent.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED); + if (networkAgent.isVPN()) { + // Temporarily disable the default proxy (not global). + synchronized (mProxyLock) { + if (!mDefaultProxyDisabled) { + mDefaultProxyDisabled = true; + if (mGlobalProxy == null && mDefaultProxy != null) { + sendProxyBroadcast(null); + } + } + } + // TODO: support proxy per network. + } } else if (state == NetworkInfo.State.DISCONNECTED || state == NetworkInfo.State.SUSPENDED) { networkAgent.asyncChannel.disconnect(); + if (networkAgent.isVPN()) { + synchronized (mProxyLock) { + if (mDefaultProxyDisabled) { + mDefaultProxyDisabled = false; + if (mGlobalProxy == null && mDefaultProxy != null) { + sendProxyBroadcast(mDefaultProxy); + } + } + } + } } } @@ -6054,9 +6035,12 @@ public class ConnectivityService extends IConnectivityManager.Stub { private LinkProperties getLinkPropertiesForTypeInternal(int networkType) { NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType); - return (nai != null) ? - new LinkProperties(nai.linkProperties) : - new LinkProperties(); + if (nai != null) { + synchronized (nai) { + return new LinkProperties(nai.linkProperties); + } + } + return new LinkProperties(); } private NetworkInfo getNetworkInfoForType(int networkType) { @@ -6075,8 +6059,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { private NetworkCapabilities getNetworkCapabilitiesForType(int networkType) { NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType); - return (nai != null) ? - new NetworkCapabilities(nai.networkCapabilities) : - new NetworkCapabilities(); + if (nai != null) { + synchronized (nai) { + return new NetworkCapabilities(nai.networkCapabilities); + } + } + return new NetworkCapabilities(); } } diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index f9c7a78271c0..c9f40cfbb71e 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -46,6 +46,7 @@ import android.net.LinkAddress; import android.net.NetworkStats; import android.net.NetworkUtils; import android.net.RouteInfo; +import android.net.UidRange; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiConfiguration.KeyMgmt; import android.os.BatteryStats; @@ -90,6 +91,7 @@ import java.net.InterfaceAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -116,6 +118,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub private static final String DEFAULT = "default"; private static final String SECONDARY = "secondary"; + private static final int MAX_UID_RANGES_PER_COMMAND = 10; + /** * Name representing {@link #setGlobalAlert(long)} limit when delivered to * {@link INetworkManagementEventObserver#limitReached(String, String)}. @@ -1702,44 +1706,46 @@ public class NetworkManagementService extends INetworkManagementService.Stub } @Override - public void setUidRangeRoute(String iface, int uid_start, int uid_end, boolean forward_dns) { + public void addVpnUidRanges(int netId, UidRange[] ranges) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); - try { - mConnector.execute("interface", "fwmark", - "uid", "add", iface, uid_start, uid_end, forward_dns ? 1 : 0); - } catch (NativeDaemonConnectorException e) { - throw e.rethrowAsParcelableException(); - } - } - - @Override - public void clearUidRangeRoute(String iface, int uid_start, int uid_end) { - mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); - try { - mConnector.execute("interface", "fwmark", - "uid", "remove", iface, uid_start, uid_end, 0); - } catch (NativeDaemonConnectorException e) { - throw e.rethrowAsParcelableException(); - } - } - - @Override - public void setMarkedForwarding(String iface) { - mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); - try { - mConnector.execute("interface", "fwmark", "rule", "add", iface); - } catch (NativeDaemonConnectorException e) { - throw e.rethrowAsParcelableException(); + Object[] argv = new Object[3 + MAX_UID_RANGES_PER_COMMAND]; + argv[0] = "users"; + argv[1] = "add"; + argv[2] = netId; + int argc = 3; + // Avoid overly long commands by limiting number of UID ranges per command. + for (int i = 0; i < ranges.length; i++) { + argv[argc++] = ranges[i].toString(); + if (i == (ranges.length - 1) || argc == argv.length) { + try { + mConnector.execute("network", Arrays.copyOf(argv, argc)); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + argc = 3; + } } } @Override - public void clearMarkedForwarding(String iface) { + public void removeVpnUidRanges(int netId, UidRange[] ranges) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); - try { - mConnector.execute("interface", "fwmark", "rule", "remove", iface); - } catch (NativeDaemonConnectorException e) { - throw e.rethrowAsParcelableException(); + Object[] argv = new Object[3 + MAX_UID_RANGES_PER_COMMAND]; + argv[0] = "users"; + argv[1] = "remove"; + argv[2] = netId; + int argc = 3; + // Avoid overly long commands by limiting number of UID ranges per command. + for (int i = 0; i < ranges.length; i++) { + argv[argc++] = ranges[i].toString(); + if (i == (ranges.length - 1) || argc == argv.length) { + try { + mConnector.execute("network", Arrays.copyOf(argv, argc)); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + argc = 3; + } } } @@ -2015,7 +2021,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub } @Override - public void createNetwork(int netId) { + public void createPhysicalNetwork(int netId) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { @@ -2026,6 +2032,17 @@ public class NetworkManagementService extends INetworkManagementService.Stub } @Override + public void createVirtualNetwork(int netId, boolean hasDNS) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + + try { + mConnector.execute("network", "create", netId, "vpn", hasDNS ? "1" : "0"); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + } + + @Override public void removeNetwork(int netId) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); @@ -2143,4 +2160,27 @@ public class NetworkManagementService extends INetworkManagementService.Stub throw e.rethrowAsParcelableException(); } } + + @Override + public void allowProtect(int uid) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + + try { + mConnector.execute("network", "protect", "allow", uid); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + } + + @Override + public void denyProtect(int uid) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + + try { + mConnector.execute("network", "protect", "deny", uid); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + } + } diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 88598c9cc533..a19eb151a6c3 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -47,8 +47,10 @@ import android.telephony.PreciseCallState; import android.telephony.PreciseDataConnectionState; import android.telephony.PreciseDisconnectCause; import android.text.TextUtils; +import android.text.format.Time; import java.util.ArrayList; +import java.util.Calendar; import java.util.List; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -354,10 +356,12 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } int phoneId = SubscriptionManager.getPhoneId(subId); r.events = events; - if (true/*DBG*/) log("listen: set events record=" + r); + if (true/*DBG*/) log("listen: set events record=" + r + " subId=" + subId + " phoneId=" + phoneId); + toStringLogSSC("listen"); if (notifyNow && validatePhoneId(phoneId)) { if ((events & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) { try { + log("listen: call onSSC state=" + mServiceState[phoneId]); r.callback.onServiceStateChanged( new ServiceState(mServiceState[phoneId])); } catch (RemoteException ex) { @@ -550,14 +554,17 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { subId = mDefaultSubId; log("notifyServiceStateUsingSubId: using mDefaultSubId=" + mDefaultSubId); } - if (true/*VDBG*/) { - log("notifyServiceStateUsingSubId: subId=" + subId - + " state=" + state); - } synchronized (mRecords) { int phoneId = SubscriptionManager.getPhoneId(subId); + if (true/*VDBG*/) { + log("notifyServiceStateUsingSubId: subId=" + subId + " phoneId=" + phoneId + + " state=" + state); + } if (validatePhoneId(phoneId)) { mServiceState[phoneId] = state; + logServiceStateChanged("notifyServiceStateUsingSubId", subId, phoneId, state); + toStringLogSSC("notifyServiceStateUsingSubId"); + for (Record r : mRecords) { log("notifyServiceStateUsingSubId: r.events=0x" + Integer.toHexString(r.events) + " r.subId=" + r.subId + " subId=" + subId + " state=" + state); // FIXME: use DEFAULT_SUB_ID instead?? @@ -591,6 +598,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { if (true/*VDBG*/) { log("notifySignalStrengthUsingSubId: subId=" + subId + " signalStrength=" + signalStrength); + toStringLogSSC("notifySignalStrengthUsingSubId"); } synchronized (mRecords) { int phoneId = SubscriptionManager.getPhoneId(subId); @@ -1295,4 +1303,59 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { private static void log(String s) { Rlog.d(TAG, s); } + + private static class LogSSC { + private Time mTime; + private String mS; + private long mSubId; + private int mPhoneId; + private ServiceState mState; + + public void set(Time t, String s, long subId, int phoneId, ServiceState state) { + mTime = t; mS = s; mSubId = subId; mPhoneId = phoneId; mState = state; + } + + @Override + public String toString() { + return mS + " " + mTime.toString() + " " + mSubId + " " + mPhoneId + " " + mState; + } + } + + private LogSSC logSSC [] = new LogSSC[10]; + private int next = 0; + + private void logServiceStateChanged(String s, long subId, int phoneId, ServiceState state) { + if (logSSC == null || logSSC.length == 0) { + return; + } + if (logSSC[next] == null) { + logSSC[next] = new LogSSC(); + } + Time t = new Time(); + t.setToNow(); + logSSC[next].set(t, s, subId, phoneId, state); + if (++next >= logSSC.length) { + next = 0; + } + } + + private void toStringLogSSC(String prompt) { + if (logSSC == null || logSSC.length == 0 || (next == 0 && logSSC[next] == null)) { + log(prompt + ": logSSC is empty"); + } else { + // There is at least one element + log(prompt + ": logSSC.length=" + logSSC.length + " next=" + next); + int i = next; + if (logSSC[i] == null) { + // logSSC is not full so back to the beginning + i = 0; + } + do { + log(logSSC[i].toString()); + if (++i >= logSSC.length) { + i = 0; + } + } while (i != next); + } + } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 053fb5a53632..5a331ac31867 100755 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -2236,6 +2236,7 @@ public final class ActivityManagerService extends ActivityManagerNative } private void start() { + Process.removeAllProcessGroups(); mProcessCpuThread.start(); mBatteryStatsService.publish(mContext); @@ -2827,6 +2828,7 @@ public final class ActivityManagerService extends ActivityManagerNative // An application record is attached to a previous process, // clean it up now. if (DEBUG_PROCESSES || DEBUG_CLEANUP) Slog.v(TAG, "App died: " + app); + Process.killProcessGroup(app.info.uid, app.pid); handleAppDiedLocked(app, true, true); } @@ -2985,7 +2987,7 @@ public final class ActivityManagerService extends ActivityManagerNative debugFlags |= Zygote.DEBUG_ENABLE_ASSERT; } - String requiredAbi = (abiOverride != null) ? abiOverride : app.info.cpuAbi; + String requiredAbi = (abiOverride != null) ? abiOverride : app.info.primaryCpuAbi; if (requiredAbi == null) { requiredAbi = Build.SUPPORTED_ABIS[0]; } @@ -4069,6 +4071,8 @@ public final class ActivityManagerService extends ActivityManagerNative stats.noteProcessDiedLocked(app.info.uid, pid); } + Process.killProcessGroup(app.info.uid, pid); + // Clean up already done if the process has been re-started. if (app.pid == pid && app.thread != null && app.thread.asBinder() == thread.asBinder()) { @@ -4307,7 +4311,10 @@ public final class ActivityManagerService extends ActivityManagerNative try { // 0 == continue, -1 = kill process immediately int res = mController.appEarlyNotResponding(app.processName, app.pid, annotation); - if (res < 0 && app.pid != MY_PID) Process.killProcess(app.pid); + if (res < 0 && app.pid != MY_PID) { + Process.killProcess(app.pid); + Process.killProcessGroup(app.info.uid, app.pid); + } } catch (RemoteException e) { mController = null; Watchdog.getInstance().setActivityController(null); @@ -4413,6 +4420,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (res != 0) { if (res < 0 && app.pid != MY_PID) { Process.killProcess(app.pid); + Process.killProcessGroup(app.info.uid, app.pid); } else { synchronized (this) { mServices.scheduleServiceTimeoutLocked(app); @@ -5122,6 +5130,7 @@ public final class ActivityManagerService extends ActivityManagerNative mBatteryStatsService.removeIsolatedUid(app.uid, app.info.uid); } killUnneededProcessLocked(app, reason); + Process.killProcessGroup(app.info.uid, app.pid); handleAppDiedLocked(app, true, allowRestart); removeLruProcessLocked(app); @@ -5210,6 +5219,7 @@ public final class ActivityManagerService extends ActivityManagerNative EventLog.writeEvent(EventLogTags.AM_DROP_PROCESS, pid); if (pid > 0 && pid != MY_PID) { Process.killProcessQuiet(pid); + //TODO: Process.killProcessGroup(app.info.uid, pid); } else { try { thread.scheduleExit(); @@ -7409,6 +7419,7 @@ public final class ActivityManagerService extends ActivityManagerNative pr.processName, pr.setAdj, reason); pr.killedByAm = true; Process.killProcessQuiet(pr.pid); + Process.killProcessGroup(pr.info.uid, pr.pid); } } @@ -10749,11 +10760,15 @@ public final class ActivityManagerService extends ActivityManagerNative try { String name = r != null ? r.processName : null; int pid = r != null ? r.pid : Binder.getCallingPid(); + int uid = r != null ? r.info.uid : Binder.getCallingUid(); if (!mController.appCrashed(name, pid, shortMsg, longMsg, timeMillis, crashInfo.stackTrace)) { Slog.w(TAG, "Force-killing crashed app " + name + " at watcher's request"); Process.killProcess(pid); + if (r != null) { + Process.killProcessGroup(uid, pid); + } return; } } catch (RemoteException e) { @@ -16587,6 +16602,7 @@ public final class ActivityManagerService extends ActivityManagerNative app.processName, app.setAdj, "empty"); app.killedByAm = true; Process.killProcessQuiet(app.pid); + Process.killProcessGroup(app.info.uid, app.pid); } else { try { app.thread.scheduleExit(); diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 13328983d31c..10bdba060e80 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -69,6 +69,10 @@ public class NetworkAgentInfo { networkRequests.put(networkRequest.requestId, networkRequest); } + public boolean isVPN() { + return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN); + } + public String toString() { return "NetworkAgentInfo{ ni{" + networkInfo + "} network{" + network + "} lp{" + diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java index 47789b1f1f87..6fb8570a2f99 100644 --- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java +++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java @@ -16,17 +16,26 @@ package com.android.server.connectivity; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.os.Handler; import android.os.Message; import android.os.SystemProperties; +import android.os.UserHandle; import android.provider.Settings; import com.android.internal.util.Protocol; import com.android.internal.util.State; import com.android.internal.util.StateMachine; +import com.android.server.ConnectivityService; import com.android.server.connectivity.NetworkAgentInfo; import java.io.BufferedReader; @@ -34,6 +43,7 @@ import java.io.InputStreamReader; import java.io.IOException; import java.io.OutputStreamWriter; import java.net.HttpURLConnection; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.net.URL; @@ -47,6 +57,19 @@ public class NetworkMonitor extends StateMachine { private static final String DEFAULT_SERVER = "clients3.google.com"; private static final int SOCKET_TIMEOUT_MS = 10000; + // Intent broadcast when user selects sign-in notification. + private static final String ACTION_SIGN_IN_REQUESTED = + "android.net.netmon.sign_in_requested"; + + // Keep these in sync with CaptivePortalLoginActivity.java. + // Intent broadcast from CaptivePortalLogin indicating sign-in is complete. + // Extras: + // EXTRA_TEXT = netId + // LOGGED_IN_RESULT = "1" if we should use network, "0" if not. + private static final String ACTION_CAPTIVE_PORTAL_LOGGED_IN = + "android.net.netmon.captive_portal_logged_in"; + private static final String LOGGED_IN_RESULT = "result"; + private static final int BASE = Protocol.BASE_NETWORK_MONITOR; /** @@ -87,35 +110,63 @@ public class NetworkMonitor extends StateMachine { public static final int EVENT_NETWORK_LINGER_COMPLETE = BASE + 5; /** - * Message to self indicating it's time to check for a captive portal again. - * TODO - Remove this once broadcast intents are used to communicate with - * apps to log into captive portals. - * arg1 = Token to ignore old messages. - */ - private static final int CMD_CAPTIVE_PORTAL_REEVALUATE = BASE + 6; - - /** * Message to self indicating it's time to evaluate a network's connectivity. * arg1 = Token to ignore old messages. */ - private static final int CMD_REEVALUATE = BASE + 7; + private static final int CMD_REEVALUATE = BASE + 6; /** * Message to self indicating network evaluation is complete. * arg1 = Token to ignore old messages. * arg2 = HTTP response code of network evaluation. */ - private static final int EVENT_REEVALUATION_COMPLETE = BASE + 8; + private static final int EVENT_REEVALUATION_COMPLETE = BASE + 7; /** * Inform NetworkMonitor that the network has disconnected. */ - public static final int CMD_NETWORK_DISCONNECTED = BASE + 9; + public static final int CMD_NETWORK_DISCONNECTED = BASE + 8; /** * Force evaluation even if it has succeeded in the past. */ - public static final int CMD_FORCE_REEVALUATION = BASE + 10; + public static final int CMD_FORCE_REEVALUATION = BASE + 9; + + /** + * Message to self indicating captive portal login is complete. + * arg1 = Token to ignore old messages. + * arg2 = 1 if we should use this network, 0 otherwise. + */ + private static final int CMD_CAPTIVE_PORTAL_LOGGED_IN = BASE + 10; + + /** + * Message to self indicating user desires to log into captive portal. + * arg1 = Token to ignore old messages. + */ + private static final int CMD_USER_WANTS_SIGN_IN = BASE + 11; + + /** + * Request ConnectivityService display provisioning notification. + * arg1 = Whether to make the notification visible. + * obj = Intent to be launched when notification selected by user. + * replyTo = NetworkAgentInfo.messenger so ConnectivityService can identify sender. + */ + public static final int EVENT_PROVISIONING_NOTIFICATION = BASE + 12; + + /** + * Message to self indicating sign-in app bypassed captive portal. + */ + private static final int EVENT_APP_BYPASSED_CAPTIVE_PORTAL = BASE + 13; + + /** + * Message to self indicating no sign-in app responded. + */ + private static final int EVENT_NO_APP_RESPONSE = BASE + 14; + + /** + * Message to self indicating sign-in app indicates sign-in is not possible. + */ + private static final int EVENT_APP_INDICATES_SIGN_IN_IMPOSSIBLE = BASE + 15; private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger"; // Default to 30s linger time-out. @@ -123,16 +174,17 @@ public class NetworkMonitor extends StateMachine { private final int mLingerDelayMs; private int mLingerToken = 0; - private static final int CAPTIVE_PORTAL_REEVALUATE_DELAY_MS = 5000; - private int mCaptivePortalReevaluateToken = 0; - // Negative values disable reevaluation. private static final String REEVALUATE_DELAY_PROPERTY = "persist.netmon.reeval_delay"; // Default to 5s reevaluation delay. private static final int DEFAULT_REEVALUATE_DELAY_MS = 5000; + private static final int MAX_RETRIES = 10; private final int mReevaluateDelayMs; private int mReevaluateToken = 0; + private int mCaptivePortalLoggedInToken = 0; + private int mUserPromptedToken = 0; + private final Context mContext; private final Handler mConnectivityServiceHandler; private final NetworkAgentInfo mNetworkAgentInfo; @@ -144,6 +196,9 @@ public class NetworkMonitor extends StateMachine { private State mOfflineState = new OfflineState(); private State mValidatedState = new ValidatedState(); private State mEvaluatingState = new EvaluatingState(); + private State mUninteractiveAppsPromptedState = new UninteractiveAppsPromptedState(); + private State mUserPromptedState = new UserPromptedState(); + private State mInteractiveAppsPromptedState = new InteractiveAppsPromptedState(); private State mCaptivePortalState = new CaptivePortalState(); private State mLingeringState = new LingeringState(); @@ -159,6 +214,9 @@ public class NetworkMonitor extends StateMachine { addState(mOfflineState, mDefaultState); addState(mValidatedState, mDefaultState); addState(mEvaluatingState, mDefaultState); + addState(mUninteractiveAppsPromptedState, mDefaultState); + addState(mUserPromptedState, mDefaultState); + addState(mInteractiveAppsPromptedState, mDefaultState); addState(mCaptivePortalState, mDefaultState); addState(mLingeringState, mDefaultState); setInitialState(mOfflineState); @@ -171,9 +229,8 @@ public class NetworkMonitor extends StateMachine { mReevaluateDelayMs = SystemProperties.getInt(REEVALUATE_DELAY_PROPERTY, DEFAULT_REEVALUATE_DELAY_MS); - // TODO: Enable this when we're ready. - // mIsCaptivePortalCheckEnabled = Settings.Global.getInt(mContext.getContentResolver(), - // Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED, 1) == 1; + mIsCaptivePortalCheckEnabled = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED, 1) == 1; start(); } @@ -237,6 +294,8 @@ public class NetworkMonitor extends StateMachine { } private class EvaluatingState extends State { + private int mRetries; + private class EvaluateInternetConnectivity extends Thread { private int mToken; EvaluateInternetConnectivity(int token) { @@ -249,6 +308,7 @@ public class NetworkMonitor extends StateMachine { @Override public void enter() { + mRetries = 0; sendMessage(CMD_REEVALUATE, ++mReevaluateToken, 0); } @@ -259,8 +319,11 @@ public class NetworkMonitor extends StateMachine { case CMD_REEVALUATE: if (message.arg1 != mReevaluateToken) break; + if (mNetworkAgentInfo.isVPN()) { + transitionTo(mValidatedState); + } // If network provides no internet connectivity adjust evaluation. - if (mNetworkAgentInfo.networkCapabilities.hasCapability( + if (!mNetworkAgentInfo.networkCapabilities.hasCapability( NetworkCapabilities.NET_CAPABILITY_INTERNET)) { // TODO: Try to verify something works. Do all gateways respond to pings? transitionTo(mValidatedState); @@ -276,12 +339,12 @@ public class NetworkMonitor extends StateMachine { if (httpResponseCode == 204) { transitionTo(mValidatedState); } else if (httpResponseCode >= 200 && httpResponseCode <= 399) { - transitionTo(mCaptivePortalState); - } else { - if (mReevaluateDelayMs >= 0) { - Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0); - sendMessageDelayed(msg, mReevaluateDelayMs); - } + transitionTo(mUninteractiveAppsPromptedState); + } else if (++mRetries > MAX_RETRIES) { + transitionTo(mOfflineState); + } else if (mReevaluateDelayMs >= 0) { + Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0); + sendMessageDelayed(msg, mReevaluateDelayMs); } break; default: @@ -291,30 +354,223 @@ public class NetworkMonitor extends StateMachine { } } - // TODO: Until we add an intent from the app handling captive portal - // login we'll just re-evaluate after a delay. + private class AppRespondedBroadcastReceiver extends BroadcastReceiver { + private static final int CAPTIVE_PORTAL_UNINITIALIZED_RETURN_CODE = 0; + private boolean mCanceled; + AppRespondedBroadcastReceiver() { + mCanceled = false; + } + public void send(String action) { + Intent intent = new Intent(action); + intent.putExtra(ConnectivityManager.EXTRA_NETWORK, mNetworkAgentInfo.network); + mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL, null, this, getHandler(), + CAPTIVE_PORTAL_UNINITIALIZED_RETURN_CODE, null, null); + } + public void cancel() { + mCanceled = true; + } + @Override + public void onReceive(Context context, Intent intent) { + if (!mCanceled) { + cancel(); + switch (getResultCode()) { + case ConnectivityManager.CAPTIVE_PORTAL_SIGNED_IN: + sendMessage(EVENT_APP_BYPASSED_CAPTIVE_PORTAL); + break; + case ConnectivityManager.CAPTIVE_PORTAL_DISCONNECT: + sendMessage(EVENT_APP_INDICATES_SIGN_IN_IMPOSSIBLE); + break; + // NOTE: This case label makes compiler enforce no overlap between result codes. + case CAPTIVE_PORTAL_UNINITIALIZED_RETURN_CODE: + default: + sendMessage(EVENT_NO_APP_RESPONSE); + break; + } + } + } + } + + private class UninteractiveAppsPromptedState extends State { + private AppRespondedBroadcastReceiver mReceiver; + @Override + public void enter() { + mReceiver = new AppRespondedBroadcastReceiver(); + mReceiver.send(ConnectivityManager.ACTION_CAPTIVE_PORTAL_DETECTED); + } + @Override + public boolean processMessage(Message message) { + if (DBG) log(getName() + message.toString()); + switch (message.what) { + case EVENT_APP_BYPASSED_CAPTIVE_PORTAL: + transitionTo(mValidatedState); + break; + case EVENT_APP_INDICATES_SIGN_IN_IMPOSSIBLE: + transitionTo(mOfflineState); + break; + case EVENT_NO_APP_RESPONSE: + transitionTo(mUserPromptedState); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + public void exit() { + mReceiver.cancel(); + } + } + + private class UserPromptedState extends State { + private class UserRespondedBroadcastReceiver extends BroadcastReceiver { + private final int mToken; + UserRespondedBroadcastReceiver(int token) { + mToken = token; + } + @Override + public void onReceive(Context context, Intent intent) { + if (Integer.parseInt(intent.getStringExtra(Intent.EXTRA_TEXT)) == + mNetworkAgentInfo.network.netId) { + sendMessage(obtainMessage(CMD_USER_WANTS_SIGN_IN, mToken)); + } + } + } + + private UserRespondedBroadcastReceiver mUserRespondedBroadcastReceiver; + + @Override + public void enter() { + // Wait for user to select sign-in notifcation. + mUserRespondedBroadcastReceiver = new UserRespondedBroadcastReceiver( + ++mUserPromptedToken); + IntentFilter filter = new IntentFilter(ACTION_SIGN_IN_REQUESTED); + mContext.registerReceiver(mUserRespondedBroadcastReceiver, filter); + // Initiate notification to sign-in. + Intent intent = new Intent(ACTION_SIGN_IN_REQUESTED); + intent.putExtra(Intent.EXTRA_TEXT, String.valueOf(mNetworkAgentInfo.network.netId)); + Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 1, 0, + PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)); + message.replyTo = mNetworkAgentInfo.messenger; + mConnectivityServiceHandler.sendMessage(message); + } + + @Override + public boolean processMessage(Message message) { + if (DBG) log(getName() + message.toString()); + switch (message.what) { + case CMD_USER_WANTS_SIGN_IN: + if (message.arg1 != mUserPromptedToken) + break; + transitionTo(mInteractiveAppsPromptedState); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + + @Override + public void exit() { + Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 0, 0, null); + message.replyTo = mNetworkAgentInfo.messenger; + mConnectivityServiceHandler.sendMessage(message); + mContext.unregisterReceiver(mUserRespondedBroadcastReceiver); + mUserRespondedBroadcastReceiver = null; + } + } + + private class InteractiveAppsPromptedState extends State { + private AppRespondedBroadcastReceiver mReceiver; + @Override + public void enter() { + mReceiver = new AppRespondedBroadcastReceiver(); + mReceiver.send(ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN); + } + @Override + public boolean processMessage(Message message) { + if (DBG) log(getName() + message.toString()); + switch (message.what) { + case EVENT_APP_BYPASSED_CAPTIVE_PORTAL: + transitionTo(mValidatedState); + break; + case EVENT_APP_INDICATES_SIGN_IN_IMPOSSIBLE: + transitionTo(mOfflineState); + break; + case EVENT_NO_APP_RESPONSE: + transitionTo(mCaptivePortalState); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + public void exit() { + mReceiver.cancel(); + } + } + private class CaptivePortalState extends State { + private class CaptivePortalLoggedInBroadcastReceiver extends BroadcastReceiver { + private final int mToken; + + CaptivePortalLoggedInBroadcastReceiver(int token) { + mToken = token; + } + + @Override + public void onReceive(Context context, Intent intent) { + if (Integer.parseInt(intent.getStringExtra(Intent.EXTRA_TEXT)) == + mNetworkAgentInfo.network.netId) { + sendMessage(obtainMessage(CMD_CAPTIVE_PORTAL_LOGGED_IN, mToken, + Integer.parseInt(intent.getStringExtra(LOGGED_IN_RESULT)))); + } + } + } + + private CaptivePortalLoggedInBroadcastReceiver mCaptivePortalLoggedInBroadcastReceiver; + @Override public void enter() { - Message message = obtainMessage(CMD_CAPTIVE_PORTAL_REEVALUATE, - ++mCaptivePortalReevaluateToken, 0); - sendMessageDelayed(message, CAPTIVE_PORTAL_REEVALUATE_DELAY_MS); + Intent intent = new Intent(Intent.ACTION_SEND); + intent.putExtra(Intent.EXTRA_TEXT, String.valueOf(mNetworkAgentInfo.network.netId)); + intent.setType("text/plain"); + intent.setComponent(new ComponentName("com.android.captiveportallogin", + "com.android.captiveportallogin.CaptivePortalLoginActivity")); + intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK); + + // Wait for result. + mCaptivePortalLoggedInBroadcastReceiver = new CaptivePortalLoggedInBroadcastReceiver( + ++mCaptivePortalLoggedInToken); + IntentFilter filter = new IntentFilter(ACTION_CAPTIVE_PORTAL_LOGGED_IN); + mContext.registerReceiver(mCaptivePortalLoggedInBroadcastReceiver, filter); + // Initiate app to log in. + mContext.startActivityAsUser(intent, UserHandle.CURRENT); } @Override public boolean processMessage(Message message) { if (DBG) log(getName() + message.toString()); switch (message.what) { - case CMD_CAPTIVE_PORTAL_REEVALUATE: - if (message.arg1 != mCaptivePortalReevaluateToken) + case CMD_CAPTIVE_PORTAL_LOGGED_IN: + if (message.arg1 != mCaptivePortalLoggedInToken) break; - transitionTo(mEvaluatingState); + if (message.arg2 == 0) { + // TODO: Should teardown network. + transitionTo(mOfflineState); + } else { + transitionTo(mValidatedState); + } break; default: return NOT_HANDLED; } return HANDLED; } + + @Override + public void exit() { + mContext.unregisterReceiver(mCaptivePortalLoggedInBroadcastReceiver); + mCaptivePortalLoggedInBroadcastReceiver = null; + } } private class LingeringState extends State { @@ -353,10 +609,12 @@ public class NetworkMonitor extends StateMachine { if (!mIsCaptivePortalCheckEnabled) return 204; String urlString = "http://" + mServer + "/generate_204"; - if (DBG) log("Checking " + urlString); + if (DBG) { + log("Checking " + urlString + " on " + mNetworkAgentInfo.networkInfo.getExtraInfo()); + } HttpURLConnection urlConnection = null; Socket socket = null; - int httpResponseCode = 500; + int httpResponseCode = 599; try { URL url = new URL(urlString); if (false) { @@ -369,25 +627,65 @@ public class NetworkMonitor extends StateMachine { urlConnection.getInputStream(); httpResponseCode = urlConnection.getResponseCode(); } else { - socket = new Socket(); - // TODO: setNetworkForSocket(socket, mNetworkAgentInfo.network.netId); - InetSocketAddress address = new InetSocketAddress(url.getHost(), 80); - // TODO: address = new InetSocketAddress( - // getByNameOnNetwork(mNetworkAgentInfo.network, url.getHost()), 80); - socket.connect(address); + socket = mNetworkAgentInfo.network.getSocketFactory().createSocket(); + socket.setSoTimeout(SOCKET_TIMEOUT_MS); + // Lookup addresses only on this Network. + InetAddress[] hostAddresses = mNetworkAgentInfo.network.getAllByName(url.getHost()); + // Try all addresses. + for (int i = 0; i < hostAddresses.length; i++) { + if (DBG) log("Connecting to " + hostAddresses[i]); + try { + socket.connect(new InetSocketAddress(hostAddresses[i], + url.getDefaultPort()), SOCKET_TIMEOUT_MS); + break; + } catch (IOException e) { + // Ignore exceptions on all but the last. + if (i == (hostAddresses.length - 1)) throw e; + } + } + if (DBG) log("Requesting " + url.getFile()); BufferedReader reader = new BufferedReader( new InputStreamReader(socket.getInputStream())); OutputStreamWriter writer = new OutputStreamWriter(socket.getOutputStream()); - writer.write("GET " + url.getFile() + " HTTP/1.1\r\n\n"); + writer.write("GET " + url.getFile() + " HTTP/1.1\r\nHost: " + url.getHost() + + "\r\nConnection: close\r\n\r\n"); writer.flush(); String response = reader.readLine(); - if (response.startsWith("HTTP/1.1 ")) { + if (DBG) log("Received \"" + response + "\""); + if (response != null && (response.startsWith("HTTP/1.1 ") || + // NOTE: We may want to consider an "HTTP/1.0 204" response to be a captive + // portal. The only example of this seen so far was a captive portal. For + // the time being go with prior behavior of assuming it's not a captive + // portal. If it is considered a captive portal, a different sign-in URL + // is needed (i.e. can't browse a 204). This could be the result of an HTTP + // proxy server. + response.startsWith("HTTP/1.0 "))) { + // NOTE: We may want to consider an "200" response with "Content-length=0" to + // not be a captive portal. This could be the result of an HTTP proxy server. + // See b/9972012. httpResponseCode = Integer.parseInt(response.substring(9, 12)); + } else { + // A response was received but not understood. The fact that a + // response was sent indicates there's some kind of responsive network + // out there so put up the notification to the user to log into the network + // so the user can have the final say as to whether the network is useful. + httpResponseCode = 399; + while (DBG && response != null && !response.isEmpty()) { + try { + response = reader.readLine(); + } catch (IOException e) { + break; + } + log("Received \"" + response + "\""); + } } } if (DBG) log("isCaptivePortal: ret=" + httpResponseCode); } catch (IOException e) { if (DBG) log("Probably not a portal: exception " + e); + if (httpResponseCode == 599) { + // TODO: Ping gateway and DNS server and log results. + } } finally { if (urlConnection != null) { urlConnection.disconnect(); diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index ec3389b8e5cb..01a2fc228c28 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -109,13 +109,14 @@ public class Tethering extends BaseNetworkObserver { // Wifi is 192.168.43.1 and 255.255.255.0 // BT is limited to max default of 5 connections. 192.168.44.1 to 192.168.48.1 // with 255.255.255.0 + // P2P is 192.168.49.1 and 255.255.255.0 private String[] mDhcpRange; private static final String[] DHCP_DEFAULT_RANGE = { "192.168.42.2", "192.168.42.254", "192.168.43.2", "192.168.43.254", "192.168.44.2", "192.168.44.254", "192.168.45.2", "192.168.45.254", "192.168.46.2", "192.168.46.254", "192.168.47.2", "192.168.47.254", - "192.168.48.2", "192.168.48.254", + "192.168.48.2", "192.168.48.254", "192.168.49.2", "192.168.49.254", }; private String[] mDefaultDnsServers; @@ -701,6 +702,10 @@ public class Tethering extends BaseNetworkObserver { return retVal; } + public String[] getTetheredDhcpRanges() { + return mDhcpRange; + } + public String[] getErroredIfaces() { ArrayList<String> list = new ArrayList<String>(); synchronized (mPublicSync) { diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index df12995d7e58..d15254b4fb0b 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -30,6 +30,7 @@ import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; import android.graphics.Bitmap; @@ -43,14 +44,18 @@ import android.net.LinkAddress; import android.net.LinkProperties; import android.net.LocalSocket; import android.net.LocalSocketAddress; +import android.net.NetworkAgent; +import android.net.NetworkCapabilities; import android.net.NetworkInfo; +import android.net.NetworkInfo.DetailedState; import android.net.NetworkUtils; import android.net.RouteInfo; -import android.net.NetworkInfo.DetailedState; +import android.net.UidRange; import android.os.Binder; import android.os.FileUtils; import android.os.IBinder; import android.os.INetworkManagementService; +import android.os.Looper; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Process; @@ -69,7 +74,6 @@ import com.android.internal.R; import com.android.internal.net.LegacyVpnInfo; import com.android.internal.net.VpnConfig; import com.android.internal.net.VpnProfile; -import com.android.server.ConnectivityService.VpnCallback; import com.android.server.net.BaseNetworkObserver; import java.io.File; @@ -78,7 +82,9 @@ import java.io.OutputStream; import java.net.InetAddress; import java.net.Inet4Address; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import libcore.io.IoUtils; @@ -86,16 +92,18 @@ import libcore.io.IoUtils; /** * @hide */ -public class Vpn extends BaseNetworkStateTracker { +public class Vpn { + private static final String NETWORKTYPE = "VPN"; private static final String TAG = "Vpn"; private static final boolean LOGD = true; - + // TODO: create separate trackers for each unique VPN to support // automated reconnection - private final VpnCallback mCallback; - - private String mPackage = VpnConfig.LEGACY_VPN; + private Context mContext; + private NetworkInfo mNetworkInfo; + private String mPackage; + private int mOwnerUID; private String mInterface; private Connection mConnection; private LegacyVpnRunner mLegacyVpnRunner; @@ -103,22 +111,29 @@ public class Vpn extends BaseNetworkStateTracker { private volatile boolean mEnableNotif = true; private volatile boolean mEnableTeardown = true; private final IConnectivityManager mConnService; + private final INetworkManagementService mNetd; private VpnConfig mConfig; + private NetworkAgent mNetworkAgent; + private final Looper mLooper; + private final NetworkCapabilities mNetworkCapabilities; /* list of users using this VPN. */ @GuardedBy("this") - private SparseBooleanArray mVpnUsers = null; + private List<UidRange> mVpnUsers = null; private BroadcastReceiver mUserIntentReceiver = null; private final int mUserId; - public Vpn(Context context, VpnCallback callback, INetworkManagementService netService, + public Vpn(Looper looper, Context context, INetworkManagementService netService, IConnectivityManager connService, int userId) { - super(ConnectivityManager.TYPE_VPN); mContext = context; - mCallback = callback; + mNetd = netService; mConnService = connService; mUserId = userId; + mLooper = looper; + + mPackage = VpnConfig.LEGACY_VPN; + mOwnerUID = getAppUid(mPackage); try { netService.registerObserver(mObserver); @@ -149,6 +164,12 @@ public class Vpn extends BaseNetworkStateTracker { mContext.registerReceiverAsUser( mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null); } + + mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_VPN, 0, NETWORKTYPE, ""); + // TODO: Copy metered attribute and bandwidths from physical transport, b/16207332 + mNetworkCapabilities = new NetworkCapabilities(); + mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_VPN); + mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN); } /** @@ -168,35 +189,15 @@ public class Vpn extends BaseNetworkStateTracker { mEnableTeardown = enableTeardown; } - @Override - protected void startMonitoringInternal() { - // Ignored; events are sent through callbacks for now - } - - @Override - public boolean teardown() { - // TODO: finish migration to unique tracker for each VPN - throw new UnsupportedOperationException(); - } - - @Override - public boolean reconnect() { - // TODO: finish migration to unique tracker for each VPN - throw new UnsupportedOperationException(); - } - - @Override - public String getTcpBufferSizesPropName() { - return PROP_TCP_BUFFER_UNKNOWN; - } - /** * Update current state, dispaching event to listeners. */ private void updateState(DetailedState detailedState, String reason) { if (LOGD) Log.d(TAG, "setting state=" + detailedState + ", reason=" + reason); mNetworkInfo.setDetailedState(detailedState, reason, null); - mCallback.onStateChanged(new NetworkInfo(mNetworkInfo)); + if (mNetworkAgent != null) { + mNetworkAgent.sendNetworkInfo(mNetworkInfo); + } } /** @@ -234,22 +235,10 @@ public class Vpn extends BaseNetworkStateTracker { // Reset the interface and hide the notification. if (mInterface != null) { - final long token = Binder.clearCallingIdentity(); - try { - mCallback.restore(); - final int size = mVpnUsers.size(); - final boolean forwardDns = (mConfig.dnsServers != null && - mConfig.dnsServers.size() != 0); - for (int i = 0; i < size; i++) { - int user = mVpnUsers.keyAt(i); - mCallback.clearUserForwarding(mInterface, user, forwardDns); - hideNotification(user); - } - - mCallback.clearMarkedForwarding(mInterface); - } finally { - Binder.restoreCallingIdentity(token); + for (UidRange uidRange : mVpnUsers) { + hideNotification(uidRange.getStartUser()); } + agentDisconnect(); jniReset(mInterface); mInterface = null; mVpnUsers = null; @@ -270,34 +259,125 @@ public class Vpn extends BaseNetworkStateTracker { mLegacyVpnRunner = null; } + long token = Binder.clearCallingIdentity(); + try { + mNetd.denyProtect(mOwnerUID); + } catch (Exception e) { + Log.wtf(TAG, "Failed to disallow UID " + mOwnerUID + " to call protect() " + e); + } finally { + Binder.restoreCallingIdentity(token); + } + Log.i(TAG, "Switched from " + mPackage + " to " + newPackage); mPackage = newPackage; + mOwnerUID = getAppUid(newPackage); + token = Binder.clearCallingIdentity(); + try { + mNetd.allowProtect(mOwnerUID); + } catch (Exception e) { + Log.wtf(TAG, "Failed to allow UID " + mOwnerUID + " to call protect() " + e); + } finally { + Binder.restoreCallingIdentity(token); + } mConfig = null; updateState(DetailedState.IDLE, "prepare"); return true; } - /** - * Protect a socket from VPN rules by binding it to the main routing table. - * The socket is NOT closed by this method. - * - * @param socket The socket to be bound. - */ - public void protect(ParcelFileDescriptor socket) throws Exception { - + private int getAppUid(String app) { + if (app == VpnConfig.LEGACY_VPN) { + return Process.myUid(); + } PackageManager pm = mContext.getPackageManager(); - int appUid = pm.getPackageUid(mPackage, mUserId); - if (Binder.getCallingUid() != appUid) { - throw new SecurityException("Unauthorized Caller"); + int result; + try { + result = pm.getPackageUid(app, mUserId); + } catch (NameNotFoundException e) { + result = -1; } - // protect the socket from routing rules - final long token = Binder.clearCallingIdentity(); + return result; + } + + public NetworkInfo getNetworkInfo() { + return mNetworkInfo; + } + + private void agentConnect() { + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName(mInterface); + boolean hasDefaultRoute = false; + for (RouteInfo route : mConfig.routes) { + lp.addRoute(route); + if (route.isDefaultRoute()) hasDefaultRoute = true; + } + if (hasDefaultRoute) { + mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + } else { + mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + } + if (mConfig.dnsServers != null) { + for (String dnsServer : mConfig.dnsServers) { + lp.addDnsServer(InetAddress.parseNumericAddress(dnsServer)); + } + } + // Concatenate search domains into a string. + StringBuilder buffer = new StringBuilder(); + if (mConfig.searchDomains != null) { + for (String domain : mConfig.searchDomains) { + buffer.append(domain).append(' '); + } + } + lp.setDomains(buffer.toString().trim()); + mNetworkInfo.setIsAvailable(true); + mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null); + long token = Binder.clearCallingIdentity(); try { - mCallback.protect(socket); + mNetworkAgent = new NetworkAgent(mLooper, mContext, NETWORKTYPE, + mNetworkInfo, mNetworkCapabilities, lp, 0) { + public void unwanted() { + // We are user controlled, not driven by NetworkRequest. + }; + }; } finally { Binder.restoreCallingIdentity(token); } + addVpnUserLocked(mUserId); + // If we are owner assign all Restricted Users to this VPN + if (mUserId == UserHandle.USER_OWNER) { + token = Binder.clearCallingIdentity(); + List<UserInfo> users; + try { + users = UserManager.get(mContext).getUsers(); + } finally { + Binder.restoreCallingIdentity(token); + } + for (UserInfo user : users) { + if (user.isRestricted()) { + addVpnUserLocked(user.id); + } + } + } + mNetworkAgent.addUidRanges(mVpnUsers.toArray(new UidRange[mVpnUsers.size()])); + } + + private void agentDisconnect(NetworkInfo networkInfo, NetworkAgent networkAgent) { + networkInfo.setIsAvailable(false); + networkInfo.setDetailedState(DetailedState.DISCONNECTED, null, null); + if (networkAgent != null) { + networkAgent.sendNetworkInfo(networkInfo); + } + } + + private void agentDisconnect(NetworkAgent networkAgent) { + NetworkInfo networkInfo = new NetworkInfo(mNetworkInfo); + agentDisconnect(networkInfo, networkAgent); + } + private void agentDisconnect() { + if (mNetworkInfo.isConnected()) { + agentDisconnect(mNetworkInfo, mNetworkAgent); + mNetworkAgent = null; + } } /** @@ -311,14 +391,7 @@ public class Vpn extends BaseNetworkStateTracker { public synchronized ParcelFileDescriptor establish(VpnConfig config) { // Check if the caller is already prepared. UserManager mgr = UserManager.get(mContext); - PackageManager pm = mContext.getPackageManager(); - ApplicationInfo app = null; - try { - app = AppGlobals.getPackageManager().getApplicationInfo(mPackage, 0, mUserId); - if (Binder.getCallingUid() != app.uid) { - return null; - } - } catch (Exception e) { + if (Binder.getCallingUid() != mOwnerUID) { return null; } // Check if the service is properly declared. @@ -350,7 +423,9 @@ public class Vpn extends BaseNetworkStateTracker { VpnConfig oldConfig = mConfig; String oldInterface = mInterface; Connection oldConnection = mConnection; - SparseBooleanArray oldUsers = mVpnUsers; + NetworkAgent oldNetworkAgent = mNetworkAgent; + mNetworkAgent = null; + List<UidRange> oldUsers = mVpnUsers; // Configure the interface. Abort if any of these steps fails. ParcelFileDescriptor tun = ParcelFileDescriptor.adoptFd(jniCreate(config.mtu)); @@ -382,67 +457,27 @@ public class Vpn extends BaseNetworkStateTracker { mConfig = config; // Set up forwarding and DNS rules. - mVpnUsers = new SparseBooleanArray(); - token = Binder.clearCallingIdentity(); - try { - mCallback.setMarkedForwarding(mInterface); - mCallback.setRoutes(mInterface, config.routes); - mCallback.override(mInterface, config.dnsServers, config.searchDomains); - addVpnUserLocked(mUserId); - // If we are owner assign all Restricted Users to this VPN - if (mUserId == UserHandle.USER_OWNER) { - for (UserInfo user : mgr.getUsers()) { - if (user.isRestricted()) { - try { - addVpnUserLocked(user.id); - } catch (Exception e) { - Log.wtf(TAG, "Failed to add user " + user.id + " to owner's VPN"); - } - } - } - } - } finally { - Binder.restoreCallingIdentity(token); - } + mVpnUsers = new ArrayList<UidRange>(); + agentConnect(); if (oldConnection != null) { mContext.unbindService(oldConnection); } + // Remove the old tun's user forwarding rules + // The new tun's user rules have already been added so they will take over + // as rules are deleted. This prevents data leakage as the rules are moved over. + agentDisconnect(oldNetworkAgent); if (oldInterface != null && !oldInterface.equals(interfaze)) { - // Remove the old tun's user forwarding rules - // The new tun's user rules have already been added so they will take over - // as rules are deleted. This prevents data leakage as the rules are moved over. - token = Binder.clearCallingIdentity(); - try { - final int size = oldUsers.size(); - final boolean forwardDns = (oldConfig.dnsServers != null && - oldConfig.dnsServers.size() != 0); - for (int i = 0; i < size; i++) { - int user = oldUsers.keyAt(i); - mCallback.clearUserForwarding(oldInterface, user, forwardDns); - } - mCallback.clearMarkedForwarding(oldInterface); - } finally { - Binder.restoreCallingIdentity(token); - } jniReset(oldInterface); } } catch (RuntimeException e) { - updateState(DetailedState.FAILED, "establish"); IoUtils.closeQuietly(tun); - // make sure marked forwarding is cleared if it was set - token = Binder.clearCallingIdentity(); - try { - mCallback.clearMarkedForwarding(mInterface); - } catch (Exception ingored) { - // ignored - } finally { - Binder.restoreCallingIdentity(token); - } + agentDisconnect(); // restore old state mConfig = oldConfig; mConnection = oldConnection; mVpnUsers = oldUsers; + mNetworkAgent = oldNetworkAgent; mInterface = oldInterface; throw e; } @@ -469,29 +504,27 @@ public class Vpn extends BaseNetworkStateTracker { return mVpnUsers != null; } + // Note: This function adds to mVpnUsers but does not publish list to NetworkAgent. private void addVpnUserLocked(int user) { - enforceControlPermission(); - if (!isRunningLocked()) { throw new IllegalStateException("VPN is not active"); } - final boolean forwardDns = (mConfig.dnsServers != null && - mConfig.dnsServers.size() != 0); - // add the user - mCallback.addUserForwarding(mInterface, user, forwardDns); - mVpnUsers.put(user, true); + mVpnUsers.add(UidRange.createForUser(user)); // show the notification if (!mPackage.equals(VpnConfig.LEGACY_VPN)) { // Load everything for the user's notification PackageManager pm = mContext.getPackageManager(); ApplicationInfo app = null; + final long token = Binder.clearCallingIdentity(); try { app = AppGlobals.getPackageManager().getApplicationInfo(mPackage, 0, mUserId); } catch (RemoteException e) { throw new IllegalStateException("Invalid application"); + } finally { + Binder.restoreCallingIdentity(token); } String label = app.loadLabel(pm).toString(); // Load the icon and convert it into a bitmap. @@ -515,15 +548,14 @@ public class Vpn extends BaseNetworkStateTracker { } private void removeVpnUserLocked(int user) { - enforceControlPermission(); - if (!isRunningLocked()) { throw new IllegalStateException("VPN is not active"); } - final boolean forwardDns = (mConfig.dnsServers != null && - mConfig.dnsServers.size() != 0); - mCallback.clearUserForwarding(mInterface, user, forwardDns); - mVpnUsers.delete(user); + UidRange uidRange = UidRange.createForUser(user); + if (mNetworkAgent != null) { + mNetworkAgent.removeUidRanges(new UidRange[] { uidRange }); + } + mVpnUsers.remove(uidRange); hideNotification(user); } @@ -535,6 +567,10 @@ public class Vpn extends BaseNetworkStateTracker { if (user.isRestricted()) { try { addVpnUserLocked(userId); + if (mNetworkAgent != null) { + UidRange uidRange = UidRange.createForUser(userId); + mNetworkAgent.addUidRanges(new UidRange[] { uidRange }); + } } catch (Exception e) { Log.wtf(TAG, "Failed to add restricted user to owner", e); } @@ -588,28 +624,15 @@ public class Vpn extends BaseNetworkStateTracker { public void interfaceRemoved(String interfaze) { synchronized (Vpn.this) { if (interfaze.equals(mInterface) && jniCheck(interfaze) == 0) { - final long token = Binder.clearCallingIdentity(); - try { - final int size = mVpnUsers.size(); - final boolean forwardDns = (mConfig.dnsServers != null && - mConfig.dnsServers.size() != 0); - for (int i = 0; i < size; i++) { - int user = mVpnUsers.keyAt(i); - mCallback.clearUserForwarding(mInterface, user, forwardDns); - hideNotification(user); - } - mVpnUsers = null; - mCallback.clearMarkedForwarding(mInterface); - - mCallback.restore(); - } finally { - Binder.restoreCallingIdentity(token); + for (UidRange uidRange : mVpnUsers) { + hideNotification(uidRange.getStartUser()); } + mVpnUsers = null; mInterface = null; if (mConnection != null) { mContext.unbindService(mConnection); mConnection = null; - updateState(DetailedState.DISCONNECTED, "interfaceRemoved"); + agentDisconnect(); } else if (mLegacyVpnRunner != null) { mLegacyVpnRunner.exit(); mLegacyVpnRunner = null; @@ -658,27 +681,32 @@ public class Vpn extends BaseNetworkStateTracker { private void showNotification(String label, Bitmap icon, int user) { if (!mEnableNotif) return; - mStatusIntent = VpnConfig.getIntentForStatusPanel(mContext); - - NotificationManager nm = (NotificationManager) - mContext.getSystemService(Context.NOTIFICATION_SERVICE); - - if (nm != null) { - String title = (label == null) ? mContext.getString(R.string.vpn_title) : - mContext.getString(R.string.vpn_title_long, label); - String text = (mConfig.session == null) ? mContext.getString(R.string.vpn_text) : - mContext.getString(R.string.vpn_text_long, mConfig.session); - - Notification notification = new Notification.Builder(mContext) - .setSmallIcon(R.drawable.vpn_connected) - .setLargeIcon(icon) - .setContentTitle(title) - .setContentText(text) - .setContentIntent(mStatusIntent) - .setDefaults(0) - .setOngoing(true) - .build(); - nm.notifyAsUser(null, R.drawable.vpn_connected, notification, new UserHandle(user)); + final long token = Binder.clearCallingIdentity(); + try { + mStatusIntent = VpnConfig.getIntentForStatusPanel(mContext); + + NotificationManager nm = (NotificationManager) + mContext.getSystemService(Context.NOTIFICATION_SERVICE); + + if (nm != null) { + String title = (label == null) ? mContext.getString(R.string.vpn_title) : + mContext.getString(R.string.vpn_title_long, label); + String text = (mConfig.session == null) ? mContext.getString(R.string.vpn_text) : + mContext.getString(R.string.vpn_text_long, mConfig.session); + + Notification notification = new Notification.Builder(mContext) + .setSmallIcon(R.drawable.vpn_connected) + .setLargeIcon(icon) + .setContentTitle(title) + .setContentText(text) + .setContentIntent(mStatusIntent) + .setDefaults(0) + .setOngoing(true) + .build(); + nm.notifyAsUser(null, R.drawable.vpn_connected, notification, new UserHandle(user)); + } + } finally { + Binder.restoreCallingIdentity(token); } } @@ -690,14 +718,18 @@ public class Vpn extends BaseNetworkStateTracker { mContext.getSystemService(Context.NOTIFICATION_SERVICE); if (nm != null) { - nm.cancelAsUser(null, R.drawable.vpn_connected, new UserHandle(user)); + final long token = Binder.clearCallingIdentity(); + try { + nm.cancelAsUser(null, R.drawable.vpn_connected, new UserHandle(user)); + } finally { + Binder.restoreCallingIdentity(token); + } } } private native int jniCreate(int mtu); private native String jniGetName(int tun); private native int jniSetAddresses(String interfaze, String addresses); - private native int jniSetRoutes(String interfaze, String routes); private native void jniReset(String interfaze); private native int jniCheck(String interfaze); @@ -959,7 +991,7 @@ public class Vpn extends BaseNetworkStateTracker { for (LocalSocket socket : mSockets) { IoUtils.closeQuietly(socket); } - updateState(DetailedState.DISCONNECTED, "exit"); + agentDisconnect(); try { mContext.unregisterReceiver(mBroadcastReceiver); } catch (IllegalArgumentException e) {} @@ -1018,7 +1050,7 @@ public class Vpn extends BaseNetworkStateTracker { restart = restart || (arguments != null); } if (!restart) { - updateState(DetailedState.DISCONNECTED, "execute"); + agentDisconnect(); return; } updateState(DetailedState.CONNECTING, "execute"); @@ -1129,15 +1161,6 @@ public class Vpn extends BaseNetworkStateTracker { } } - // Set the routes. - long token = Binder.clearCallingIdentity(); - try { - mCallback.setMarkedForwarding(mConfig.interfaze); - mCallback.setRoutes(mConfig.interfaze, mConfig.routes); - } finally { - Binder.restoreCallingIdentity(token); - } - // Here is the last step and it must be done synchronously. synchronized (Vpn.this) { // Set the start time @@ -1153,44 +1176,14 @@ public class Vpn extends BaseNetworkStateTracker { // Now INetworkManagementEventObserver is watching our back. mInterface = mConfig.interfaze; - mVpnUsers = new SparseBooleanArray(); - - token = Binder.clearCallingIdentity(); - try { - mCallback.override(mInterface, mConfig.dnsServers, mConfig.searchDomains); - addVpnUserLocked(mUserId); - } finally { - Binder.restoreCallingIdentity(token); - } + mVpnUsers = new ArrayList<UidRange>(); + + agentConnect(); - // Assign all restircted users to this VPN - // (Legacy VPNs are Owner only) - UserManager mgr = UserManager.get(mContext); - token = Binder.clearCallingIdentity(); - try { - for (UserInfo user : mgr.getUsers()) { - if (user.isRestricted()) { - try { - addVpnUserLocked(user.id); - } catch (Exception e) { - Log.wtf(TAG, "Failed to add user " + user.id - + " to owner's VPN"); - } - } - } - } finally { - Binder.restoreCallingIdentity(token); - } Log.i(TAG, "Connected!"); - updateState(DetailedState.CONNECTED, "execute"); } } catch (Exception e) { Log.i(TAG, "Aborting", e); - // make sure the routing is cleared - try { - mCallback.clearMarkedForwarding(mConfig.interfaze); - } catch (Exception ignored) { - } exit(); } finally { // Kill the daemons if they fail to stop. @@ -1202,7 +1195,7 @@ public class Vpn extends BaseNetworkStateTracker { // Do not leave an unstable state. if (!initFinished || mNetworkInfo.getDetailedState() == DetailedState.CONNECTING) { - updateState(DetailedState.FAILED, "execute"); + agentDisconnect(); } } } @@ -1232,7 +1225,7 @@ public class Vpn extends BaseNetworkStateTracker { SystemService.stop(daemon); } - updateState(DetailedState.DISCONNECTED, "babysit"); + agentDisconnect(); } } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java index a5a502a7365e..b1496cc8c31c 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecController.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java @@ -276,6 +276,12 @@ final class HdmiCecController { nativeClearLogicalAddress(mNativePtr); } + @ServiceThreadOnly + void clearLocalDevices() { + assertRunOnServiceThread(); + mLocalDevices.clear(); + } + /** * Return the physical address of the device. * diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index 0cae459491e3..07a564dc0d86 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -17,7 +17,9 @@ package com.android.server.hdmi; import android.hardware.hdmi.HdmiCecDeviceInfo; +import android.os.Handler; import android.os.Looper; +import android.os.Message; import android.util.Slog; import com.android.internal.annotations.GuardedBy; @@ -35,6 +37,11 @@ import java.util.List; abstract class HdmiCecLocalDevice { private static final String TAG = "HdmiCecLocalDevice"; + private static final int MSG_DISABLE_DEVICE_TIMEOUT = 1; + // Timeout in millisecond for device clean up (5s). + // Normal actions timeout is 2s but some of them would have several sequence of timeout. + private static final int DEVICE_CLEANUP_TIMEOUT = 5000; + protected final HdmiControlService mService; protected final int mDeviceType; protected int mAddress; @@ -58,6 +65,27 @@ abstract class HdmiCecLocalDevice { // Note that access to this collection should happen in service thread. private final LinkedList<FeatureAction> mActions = new LinkedList<>(); + private Handler mHandler = new Handler () { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_DISABLE_DEVICE_TIMEOUT: + handleDisableDeviceTimeout(); + break; + } + } + }; + + /** + * A callback interface to get notified when all pending action is cleared. + * It can be called when timeout happened. + */ + interface PendingActionClearedCallback { + void onCleared(HdmiCecLocalDevice device); + } + + protected PendingActionClearedCallback mPendingActionClearedCallback; + protected HdmiCecLocalDevice(HdmiControlService service, int deviceType) { mService = service; mDeviceType = deviceType; @@ -482,10 +510,11 @@ abstract class HdmiCecLocalDevice { } protected void checkIfPendingActionsCleared() { - if (mActions.isEmpty()) { - mService.onPendingActionsCleared(); + if (mActions.isEmpty() && mPendingActionClearedCallback != null) { + mPendingActionClearedCallback.onCleared(this); } } + protected void assertRunOnServiceThread() { if (Looper.myLooper() != mService.getServiceLooper()) { throw new IllegalStateException("Should run on service thread."); @@ -578,21 +607,48 @@ abstract class HdmiCecLocalDevice { } /** - * Called when the system started transition to standby mode. + * Called when the system goes to standby mode. * * @param initiatedByCec true if this power sequence is initiated - * by the reception the CEC messages like <StandBy> + * by the reception the CEC messages like <Standby> */ - protected void onTransitionToStandby(boolean initiatedByCec) { - // If there are no outstanding actions, we'll go to STANDBY state. - checkIfPendingActionsCleared(); + protected void onStandby(boolean initiatedByCec) {} + + /** + * Disable device. {@code callback} is used to get notified when all pending + * actions are completed or timeout is issued. + * + * @param initiatedByCec true if this sequence is initiated + * by the reception the CEC messages like <Standby> + * @param callback callback interface to get notified when all pending actions are cleared + */ + protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) { + mPendingActionClearedCallback = callback; + mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_DISABLE_DEVICE_TIMEOUT), + DEVICE_CLEANUP_TIMEOUT); + } + + @ServiceThreadOnly + private void handleDisableDeviceTimeout() { + assertRunOnServiceThread(); + + // If all actions are not cleared in DEVICE_CLEANUP_TIMEOUT, enforce to finish them. + // onCleard will be called at the last action's finish method. + Iterator<FeatureAction> iter = mActions.iterator(); + while (iter.hasNext()) { + FeatureAction action = iter.next(); + action.finish(); + iter.remove(); + } } /** - * Called when the system goes to standby mode. + * Send a key event to other device. * - * @param initiatedByCec true if this power sequence is initiated - * by the reception the CEC messages like <StandBy> + * @param keyCode key code defined in {@link android.view.KeyEvent} + * @param isPressed {@code true} for key down event */ - protected void onStandBy(boolean initiatedByCec) {} + protected void sendKeyEvent(int keyCode, boolean isPressed) { + Slog.w(TAG, "sendKeyEvent not implemented"); + } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java index 7ab2e8c081df..9807659f6b1b 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java @@ -141,7 +141,9 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { @Override @ServiceThreadOnly - protected void onTransitionToStandby(boolean initiatedByCec) { + protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) { + super.disableDevice(initiatedByCec, callback); + assertRunOnServiceThread(); if (!initiatedByCec && mIsActiveSource) { mService.sendCecCommand(HdmiCecMessageBuilder.buildInactiveSource( diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 4185d25a87e6..c8c7401ba66b 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -224,10 +224,11 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { * Sends key to a target CEC device. * * @param keyCode key code to send. Defined in {@link android.view.KeyEvent}. - * @param isPressed true if this is keypress event + * @param isPressed true if this is key press event */ + @Override @ServiceThreadOnly - void sendKeyEvent(int keyCode, boolean isPressed) { + protected void sendKeyEvent(int keyCode, boolean isPressed) { assertRunOnServiceThread(); List<SendKeyAction> action = getActions(SendKeyAction.class); if (!action.isEmpty()) { @@ -1033,19 +1034,58 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @Override @ServiceThreadOnly - protected void onTransitionToStandby(boolean initiatedByCec) { + protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) { + super.disableDevice(initiatedByCec, callback); assertRunOnServiceThread(); // Remove any repeated working actions. // HotplugDetectionAction will be reinstated during the wake up process. // HdmiControlService.onWakeUp() -> initializeLocalDevices() -> // LocalDeviceTv.onAddressAllocated() -> launchDeviceDiscovery(). + removeAction(DeviceDiscoveryAction.class); removeAction(HotplugDetectionAction.class); + + disableSystemAudioIfExist(); + disableArcIfExist(); checkIfPendingActionsCleared(); } + @ServiceThreadOnly + private void disableSystemAudioIfExist() { + assertRunOnServiceThread(); + if (getAvrDeviceInfo() == null) { + return; + } + + // Seq #31. + removeAction(SystemAudioActionFromAvr.class); + removeAction(SystemAudioActionFromTv.class); + removeAction(SystemAudioAutoInitiationAction.class); + removeAction(SystemAudioStatusAction.class); + removeAction(VolumeControlAction.class); + + // Once adding additional param which describes whether to record it to NVM or not to this + // method, put "false" for it. + setSystemAudioMode(false); + } + + @ServiceThreadOnly + private void disableArcIfExist() { + assertRunOnServiceThread(); + HdmiCecDeviceInfo avr = getAvrDeviceInfo(); + if (avr == null) { + return; + } + + // Seq #44. + removeAction(RequestArcInitiationAction.class); + if (!hasAction(RequestArcTerminationAction.class) && isArcEstabilished()) { + addAndStartAction(new RequestArcTerminationAction(this, avr.getLogicalAddress())); + } + } + @Override @ServiceThreadOnly - protected void onStandBy(boolean initiatedByCec) { + protected void onStandby(boolean initiatedByCec) { assertRunOnServiceThread(); // Seq #11 if (!mService.isControlEnabled()) { diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java index 57f89b1a26a3..0b6c3c5f105b 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java @@ -274,7 +274,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildInactiveSource(int src, int physicalAddress) { - return buildCommand(src, Constants.ADDR_BROADCAST, + return buildCommand(src, Constants.ADDR_TV, Constants.MESSAGE_INACTIVE_SOURCE, physicalAddressToParam(physicalAddress)); } diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index d35f03dacfef..b0155f5cdec2 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -50,6 +50,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.server.SystemService; import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback; +import com.android.server.hdmi.HdmiCecLocalDevice.PendingActionClearedCallback; import java.util.ArrayList; import java.util.Collections; @@ -72,9 +73,11 @@ public final class HdmiControlService extends SystemService { * Called when {@link HdmiControlService#sendCecCommand} is completed. * * @param error result of send request. - * @see {@link #SEND_RESULT_SUCCESS} - * @see {@link #SEND_RESULT_NAK} - * @see {@link #SEND_RESULT_FAILURE} + * <ul> + * <li>{@link Constants#SEND_RESULT_SUCCESS} + * <li>{@link Constants#SEND_RESULT_NAK} + * <li>{@link Constants#SEND_RESULT_FAILURE} + * </ul> */ void onSendCompleted(int error); } @@ -691,19 +694,17 @@ public final class HdmiControlService extends SystemService { } @Override - public void sendKeyEvent(final int keyCode, final boolean isPressed) { + public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) { enforceAccessPermission(); runOnServiceThread(new Runnable() { @Override public void run() { - // TODO: sendKeyEvent is for TV device only for now. Allow other - // local devices of different types to use this as well. - HdmiCecLocalDeviceTv tv = tv(); - if (tv == null) { - Slog.w(TAG, "Local tv device not available"); + HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType); + if (localDevice == null) { + Slog.w(TAG, "Local device not available"); return; } - tv.sendKeyEvent(keyCode, isPressed); + localDevice.sendKeyEvent(keyCode, isPressed); } }); } @@ -841,27 +842,11 @@ public final class HdmiControlService extends SystemService { @Override public void setControlEnabled(final boolean enabled) { enforceAccessPermission(); - synchronized (mLock) { - mHdmiControlEnabled = enabled; - } - // TODO: Stop the running actions when disabled, and start - // address allocation/device discovery when enabled. - if (!enabled) { - return; - } runOnServiceThread(new Runnable() { @Override public void run() { - HdmiCecLocalDeviceTv tv = tv(); - if (tv == null) { - return; - } - int value = enabled ? HdmiTvClient.ENABLED : HdmiTvClient.DISABLED; - mCecController.setOption(HdmiTvClient.OPTION_CEC_ENABLE, value); - if (mMhlController != null) { - mMhlController.setOption(HdmiTvClient.OPTION_MHL_ENABLE, value); - } - tv.launchRoutingControl(false); + handleHdmiControlStatusChanged(enabled); + } }); } @@ -1241,25 +1226,48 @@ public final class HdmiControlService extends SystemService { private void onStandby() { assertRunOnServiceThread(); mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY; + + final List<HdmiCecLocalDevice> devices = getAllLocalDevices(); + disableDevices(new PendingActionClearedCallback() { + @Override + public void onCleared(HdmiCecLocalDevice device) { + Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType); + devices.remove(device); + if (devices.isEmpty()) { + clearLocalDevices(); + onStandbyCompleted(); + } + } + }); + } + + private void disableDevices(PendingActionClearedCallback callback) { for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { - device.onTransitionToStandby(mStandbyMessageReceived); + device.disableDevice(mStandbyMessageReceived, callback); } } - /** - * Called when there are the outstanding actions in the local devices. - * This callback is used to wait for when the action queue is empty - * during the power state transition to standby. - */ @ServiceThreadOnly - void onPendingActionsCleared() { + private void clearLocalDevices() { + assertRunOnServiceThread(); + if (mCecController == null) { + return; + } + mCecController.clearLogicalAddress(); + mCecController.clearLocalDevices(); + } + + @ServiceThreadOnly + private void onStandbyCompleted() { assertRunOnServiceThread(); + Slog.v(TAG, "onStandbyCompleted"); + if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) { return; } mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY; for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { - device.onStandBy(mStandbyMessageReceived); + device.onStandby(mStandbyMessageReceived); } mStandbyMessageReceived = false; mCecController.setOption(HdmiTvClient.OPTION_CEC_SERVICE_CONTROL, HdmiTvClient.DISABLED); @@ -1305,4 +1313,36 @@ public final class HdmiControlService extends SystemService { mProhibitMode = enabled; } } + + @ServiceThreadOnly + private void handleHdmiControlStatusChanged(boolean enabled) { + assertRunOnServiceThread(); + + int value = enabled ? HdmiTvClient.ENABLED : HdmiTvClient.DISABLED; + mCecController.setOption(HdmiTvClient.OPTION_CEC_ENABLE, value); + if (mMhlController != null) { + mMhlController.setOption(HdmiTvClient.OPTION_MHL_ENABLE, value); + } + + synchronized (mLock) { + mHdmiControlEnabled = enabled; + } + + if (enabled) { + // TODO: call initalizedLocalDevice with additional param once putting + // it to address allocation result. + HdmiCecLocalDeviceTv tv = tv(); + if (tv != null) { + tv.launchRoutingControl(false); + } + } else { + disableDevices(new PendingActionClearedCallback() { + @Override + public void onCleared(HdmiCecLocalDevice device) { + assertRunOnServiceThread(); + clearLocalDevices(); + } + }); + } + } } diff --git a/services/core/java/com/android/server/location/FlpHardwareProvider.java b/services/core/java/com/android/server/location/FlpHardwareProvider.java index 2699feafae0d..495d3a9773c1 100644 --- a/services/core/java/com/android/server/location/FlpHardwareProvider.java +++ b/services/core/java/com/android/server/location/FlpHardwareProvider.java @@ -420,9 +420,8 @@ public class FlpHardwareProvider { return GeofenceHardware.GEOFENCE_SUCCESS; case FLP_RESULT_ERROR: return GeofenceHardware.GEOFENCE_FAILURE; - // TODO: uncomment this once the ERROR definition is marked public - //case FLP_RESULT_INSUFFICIENT_MEMORY: - // return GeofenceHardware.GEOFENCE_ERROR_INSUFFICIENT_MEMORY; + case FLP_RESULT_INSUFFICIENT_MEMORY: + return GeofenceHardware.GEOFENCE_ERROR_INSUFFICIENT_MEMORY; case FLP_RESULT_TOO_MANY_GEOFENCES: return GeofenceHardware.GEOFENCE_ERROR_TOO_MANY_GEOFENCES; case FLP_RESULT_ID_EXISTS: diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index aeade50b019c..0fbcd7efcf72 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -68,18 +68,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { private static final boolean DEBUG = false; /** - * These are the playback states that count as currently active. - */ - private static final int[] ACTIVE_STATES = { - PlaybackState.STATE_FAST_FORWARDING, - PlaybackState.STATE_REWINDING, - PlaybackState.STATE_SKIPPING_TO_PREVIOUS, - PlaybackState.STATE_SKIPPING_TO_NEXT, - PlaybackState.STATE_BUFFERING, - PlaybackState.STATE_CONNECTING, - PlaybackState.STATE_PLAYING }; - - /** * The length of time a session will still be considered active after * pausing in ms. */ @@ -402,7 +390,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { */ public boolean isPlaybackActive(boolean includeRecentlyActive) { int state = mPlaybackState == null ? 0 : mPlaybackState.getState(); - if (isActiveState(state)) { + if (MediaSession.isActiveState(state)) { return true; } if (includeRecentlyActive && state == mPlaybackState.STATE_PAUSED) { @@ -555,15 +543,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { pw.println(indent + "params=" + (mRequest == null ? null : mRequest.toString())); } - private boolean isActiveState(int state) { - for (int i = 0; i < ACTIVE_STATES.length; i++) { - if (ACTIVE_STATES[i] == state) { - return true; - } - } - return false; - } - private String getShortMetadataString() { int fields = mMetadata == null ? 0 : mMetadata.size(); String title = mMetadata == null ? null : mMetadata @@ -804,7 +783,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { public void setPlaybackState(PlaybackState state) { int oldState = mPlaybackState == null ? 0 : mPlaybackState.getState(); int newState = state == null ? 0 : state.getState(); - if (isActiveState(oldState) && newState == PlaybackState.STATE_PAUSED) { + if (MediaSession.isActiveState(oldState) && newState == PlaybackState.STATE_PAUSED) { mLastActiveTime = SystemClock.elapsedRealtime(); } mPlaybackState = state; diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 5a16e4ddb9bd..310f3e998127 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -27,6 +27,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.media.AudioManager; import android.media.IAudioService; import android.media.IRemoteVolumeController; import android.media.routeprovider.RouteRequest; @@ -948,6 +949,12 @@ public class MediaSessionService extends SystemService implements Monitor { } if (session == null) { + if ((flags & AudioManager.FLAG_ACTIVE_MEDIA_ONLY) != 0) { + if (DEBUG) { + Log.d(TAG, "No active session to adjust, skipping media only volume event"); + return; + } + } try { if (delta == 0) { mAudioService.adjustSuggestedStreamVolume(delta, suggestedStream, flags, diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 3a6cec22f0b4..892f61f3e96b 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2301,7 +2301,7 @@ public class NotificationManagerService extends SystemService { int speedBumpIndex = -1; final int N = mNotificationList.size(); ArrayList<String> keys = new ArrayList<String>(N); - ArrayList<String> dndKeys = new ArrayList<String>(N); + ArrayList<String> interceptedKeys = new ArrayList<String>(N); for (int i = 0; i < N; i++) { NotificationRecord record = mNotificationList.get(i); if (!info.enabledAndUserMatches(record.sbn.getUserId())) { @@ -2309,7 +2309,7 @@ public class NotificationManagerService extends SystemService { } keys.add(record.sbn.getKey()); if (record.isIntercepted()) { - dndKeys.add(record.sbn.getKey()); + interceptedKeys.add(record.sbn.getKey()); } if (speedBumpIndex == -1 && record.sbn.getNotification().priority == Notification.PRIORITY_MIN) { @@ -2317,8 +2317,8 @@ public class NotificationManagerService extends SystemService { } } String[] keysAr = keys.toArray(new String[keys.size()]); - String[] dndKeysAr = dndKeys.toArray(new String[dndKeys.size()]); - return new NotificationRankingUpdate(keysAr, dndKeysAr, speedBumpIndex); + String[] interceptedKeysAr = interceptedKeys.toArray(new String[interceptedKeys.size()]); + return new NotificationRankingUpdate(keysAr, interceptedKeysAr, speedBumpIndex); } public class NotificationListeners extends ManagedServices { diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 6ad2e6002753..1193968ac6be 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -365,7 +365,7 @@ public final class Installer extends SystemService { } public int getSizeInfo(String pkgName, int persona, String apkPath, String libDirPath, - String fwdLockApkPath, String asecPath, String instructionSet, PackageStats pStats) { + String fwdLockApkPath, String asecPath, String[] instructionSets, PackageStats pStats) { StringBuilder builder = new StringBuilder("getsize"); builder.append(' '); builder.append(pkgName); @@ -374,13 +374,17 @@ public final class Installer extends SystemService { builder.append(' '); builder.append(apkPath); builder.append(' '); + // TODO: Extend getSizeInfo to look at the full subdirectory tree, + // not just the first level. builder.append(libDirPath != null ? libDirPath : "!"); builder.append(' '); builder.append(fwdLockApkPath != null ? fwdLockApkPath : "!"); builder.append(' '); builder.append(asecPath != null ? asecPath : "!"); builder.append(' '); - builder.append(instructionSet); + // TODO: Extend getSizeInfo to look at *all* instrution sets, not + // just the primary. + builder.append(instructionSets[0]); String s = transaction(builder.toString()); String res[] = s.split(" "); @@ -404,18 +408,17 @@ public final class Installer extends SystemService { } /** - * Links the native library directory in an application's directory to its - * real location. + * Links the 32 bit native library directory in an application's data directory to the + * real location for backward compatibility. Note that no such symlink is created for + * 64 bit shared libraries. * - * @param dataPath data directory where the application is - * @param nativeLibPath target native library path * @return -1 on error */ - public int linkNativeLibraryDirectory(String dataPath, String nativeLibPath, int userId) { + public int linkNativeLibraryDirectory(String dataPath, String nativeLibPath32, int userId) { if (dataPath == null) { Slog.e(TAG, "linkNativeLibraryDirectory dataPath is null"); return -1; - } else if (nativeLibPath == null) { + } else if (nativeLibPath32 == null) { Slog.e(TAG, "linkNativeLibraryDirectory nativeLibPath is null"); return -1; } @@ -423,7 +426,7 @@ public final class Installer extends SystemService { StringBuilder builder = new StringBuilder("linklib "); builder.append(dataPath); builder.append(' '); - builder.append(nativeLibPath); + builder.append(nativeLibPath32); builder.append(' '); builder.append(userId); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index fe9ce8ffd471..53e20bb9771c 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -18,7 +18,6 @@ package com.android.server.pm; import static android.Manifest.permission.GRANT_REVOKE_PERMISSIONS; import static android.Manifest.permission.READ_EXTERNAL_STORAGE; -import static android.Manifest.permission.INSTALL_PACKAGES; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED; @@ -34,7 +33,6 @@ import static android.system.OsConstants.O_CREAT; import static android.system.OsConstants.EEXIST; import static android.system.OsConstants.O_EXCL; import static android.system.OsConstants.O_RDWR; -import static android.system.OsConstants.O_WRONLY; import static android.system.OsConstants.S_IRGRP; import static android.system.OsConstants.S_IROTH; import static android.system.OsConstants.S_IRWXU; @@ -57,7 +55,6 @@ import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.Preconditions; -import com.android.internal.util.XmlUtils; import com.android.server.EventLogTags; import com.android.server.IntentResolver; import com.android.server.LocalServices; @@ -67,14 +64,11 @@ import com.android.server.Watchdog; import com.android.server.pm.Settings.DatabaseVersion; import com.android.server.storage.DeviceStorageMonitorInternal; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import android.app.ActivityManager; import android.app.ActivityManagerNative; import android.app.IActivityManager; -import android.app.PackageInstallObserver; import android.app.admin.IDevicePolicyManager; import android.app.backup.IBackupManager; import android.content.BroadcastReceiver; @@ -149,7 +143,6 @@ import android.security.KeyStore; import android.security.SystemKeyStore; import android.system.ErrnoException; import android.system.Os; -import android.system.OsConstants; import android.system.StructStat; import android.text.TextUtils; import android.util.ArraySet; @@ -162,7 +155,6 @@ import android.util.PrintStreamPrinter; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; -import android.util.Xml; import android.view.Display; import java.io.BufferedInputStream; @@ -172,7 +164,6 @@ import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; -import java.io.FileReader; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; @@ -204,7 +195,6 @@ import dalvik.system.StaleDexCacheError; import dalvik.system.VMRuntime; import libcore.io.IoUtils; -import libcore.io.Libcore; /** * Keep track of all those .apks everywhere. @@ -233,6 +223,7 @@ public class PackageManagerService extends IPackageManager.Stub { private static final boolean DEBUG_APP_DIR_OBSERVER = false; private static final boolean DEBUG_VERIFY = false; private static final boolean DEBUG_DEXOPT = false; + private static final boolean DEBUG_ABI_SELECTION = false; private static final int RADIO_UID = Process.PHONE_UID; private static final int LOG_UID = Process.LOG_UID; @@ -368,10 +359,10 @@ public class PackageManagerService extends IPackageManager.Stub { final File mAppInstallDir; /** - * Directory to which applications installed internally have native - * libraries copied. + * Directory to which applications installed internally have their + * 32 bit native libraries copied. */ - private File mAppLibInstallDir; + private File mAppLib32InstallDir; // Directory containing the private parts (e.g. code and non-resource assets) of forward-locked // apps. @@ -1354,7 +1345,7 @@ public class PackageManagerService extends IPackageManager.Stub { File dataDir = Environment.getDataDirectory(); mAppDataDir = new File(dataDir, "data"); mAppInstallDir = new File(dataDir, "app"); - mAppLibInstallDir = new File(dataDir, "app-lib"); + mAppLib32InstallDir = new File(dataDir, "app-lib"); mAsecInternalPath = new File(dataDir, "app-asec").getPath(); mUserAppDataDir = new File(dataDir, "user"); mDrmAppPrivateInstallDir = new File(dataDir, "app-private"); @@ -2089,7 +2080,8 @@ public class PackageManagerService extends IPackageManager.Stub { pkg.applicationInfo.flags = ps.pkgFlags | ApplicationInfo.FLAG_IS_DATA_ONLY; pkg.applicationInfo.dataDir = getDataPathForPackage(packageName, 0).getPath(); - pkg.applicationInfo.cpuAbi = ps.cpuAbiString; + pkg.applicationInfo.primaryCpuAbi = ps.primaryCpuAbiString; + pkg.applicationInfo.secondaryCpuAbi = ps.secondaryCpuAbiString; } return generatePackageInfo(pkg, flags, userId); } @@ -4269,8 +4261,8 @@ public class PackageManagerService extends IPackageManager.Stub { + " better than installed " + ps.versionCode); InstallArgs args = createInstallArgsForExisting(packageFlagsToInstallFlags(ps), - ps.codePathString, ps.resourcePathString, ps.nativeLibraryPathString, - getAppInstructionSetFromSettings(ps)); + ps.codePathString, ps.resourcePathString, ps.legacyNativeLibraryPathString, + getAppDexInstructionSets(ps), isMultiArch(ps)); synchronized (mInstallLock) { args.cleanUpResourcesLI(); } @@ -4334,8 +4326,8 @@ public class PackageManagerService extends IPackageManager.Stub { + ps.codePathString + ": new version " + pkg.mVersionCode + " better than installed " + ps.versionCode); InstallArgs args = createInstallArgsForExisting(packageFlagsToInstallFlags(ps), - ps.codePathString, ps.resourcePathString, ps.nativeLibraryPathString, - getAppInstructionSetFromSettings(ps)); + ps.codePathString, ps.resourcePathString, ps.legacyNativeLibraryPathString, + getAppDexInstructionSets(ps), isMultiArch(ps)); synchronized (mInstallLock) { args.cleanUpResourcesLI(); } @@ -4585,7 +4577,7 @@ public class PackageManagerService extends IPackageManager.Stub { mPackageUsage.write(true); } - private void performDexOptLibsLI(ArrayList<String> libs, String instructionSet, + private void performDexOptLibsLI(ArrayList<String> libs, String[] instructionSets, boolean forceDex, boolean defer, HashSet<String> done) { for (int i=0; i<libs.size(); i++) { PackageParser.Package libPkg; @@ -4600,7 +4592,7 @@ public class PackageManagerService extends IPackageManager.Stub { } } if (libPkg != null && !done.contains(libName)) { - performDexOptLI(libPkg, instructionSet, forceDex, defer, done); + performDexOptLI(libPkg, instructionSets, forceDex, defer, done); } } } @@ -4610,89 +4602,98 @@ public class PackageManagerService extends IPackageManager.Stub { static final int DEX_OPT_DEFERRED = 2; static final int DEX_OPT_FAILED = -1; - private int performDexOptLI(PackageParser.Package pkg, String instructionSetOverride, + private int performDexOptLI(PackageParser.Package pkg, String[] targetInstructionSets, boolean forceDex, boolean defer, HashSet<String> done) { - final String instructionSet = instructionSetOverride != null ? - instructionSetOverride : getAppInstructionSet(pkg.applicationInfo); + final String[] instructionSets = targetInstructionSets != null ? + targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo); if (done != null) { done.add(pkg.packageName); if (pkg.usesLibraries != null) { - performDexOptLibsLI(pkg.usesLibraries, instructionSet, forceDex, defer, done); + performDexOptLibsLI(pkg.usesLibraries, instructionSets, forceDex, defer, done); } if (pkg.usesOptionalLibraries != null) { - performDexOptLibsLI(pkg.usesOptionalLibraries, instructionSet, forceDex, defer, done); + performDexOptLibsLI(pkg.usesOptionalLibraries, instructionSets, forceDex, defer, done); } } if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0) { final Collection<String> paths = pkg.getAllCodePaths(); for (String path : paths) { - try { - boolean isDexOptNeededInternal = DexFile.isDexOptNeededInternal(path, - pkg.packageName, instructionSet, defer); - // There are three basic cases here: - // 1.) we need to dexopt, either because we are forced or it is needed - // 2.) we are defering a needed dexopt - // 3.) we are skipping an unneeded dexopt - if (forceDex || (!defer && isDexOptNeededInternal)) { - Log.i(TAG, "Running dexopt on: " + pkg.applicationInfo.packageName); - final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid); - int ret = mInstaller.dexopt(path, sharedGid, !isForwardLocked(pkg), - pkg.packageName, instructionSet); - // Note that we ran dexopt, since rerunning will - // probably just result in an error again. - pkg.mDexOptNeeded = false; - if (ret < 0) { - return DEX_OPT_FAILED; + for (String instructionSet : instructionSets) { + try { + boolean isDexOptNeededInternal = DexFile.isDexOptNeededInternal(path, + pkg.packageName, instructionSet, defer); + // There are three basic cases here: + // 1.) we need to dexopt, either because we are forced or it is needed + // 2.) we are defering a needed dexopt + // 3.) we are skipping an unneeded dexopt + if (forceDex || (!defer && isDexOptNeededInternal)) { + Log.i(TAG, "Running dexopt on: " + pkg.applicationInfo.packageName); + final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid); + int ret = mInstaller.dexopt(path, sharedGid, !isForwardLocked(pkg), + pkg.packageName, instructionSet); + // Note that we ran dexopt, since rerunning will + // probably just result in an error again. + pkg.mDexOptNeeded = false; + if (ret < 0) { + return DEX_OPT_FAILED; + } + return DEX_OPT_PERFORMED; } - return DEX_OPT_PERFORMED; - } - if (defer && isDexOptNeededInternal) { - if (mDeferredDexOpt == null) { - mDeferredDexOpt = new HashSet<PackageParser.Package>(); + if (defer && isDexOptNeededInternal) { + if (mDeferredDexOpt == null) { + mDeferredDexOpt = new HashSet<PackageParser.Package>(); + } + mDeferredDexOpt.add(pkg); + return DEX_OPT_DEFERRED; } - mDeferredDexOpt.add(pkg); - return DEX_OPT_DEFERRED; + pkg.mDexOptNeeded = false; + return DEX_OPT_SKIPPED; + } catch (FileNotFoundException e) { + Slog.w(TAG, "Apk not found for dexopt: " + path); + return DEX_OPT_FAILED; + } catch (IOException e) { + Slog.w(TAG, "IOException reading apk: " + path, e); + return DEX_OPT_FAILED; + } catch (StaleDexCacheError e) { + Slog.w(TAG, "StaleDexCacheError when reading apk: " + path, e); + return DEX_OPT_FAILED; + } catch (Exception e) { + Slog.w(TAG, "Exception when doing dexopt : ", e); + return DEX_OPT_FAILED; } - pkg.mDexOptNeeded = false; - return DEX_OPT_SKIPPED; - } catch (FileNotFoundException e) { - Slog.w(TAG, "Apk not found for dexopt: " + path); - return DEX_OPT_FAILED; - } catch (IOException e) { - Slog.w(TAG, "IOException reading apk: " + path, e); - return DEX_OPT_FAILED; - } catch (StaleDexCacheError e) { - Slog.w(TAG, "StaleDexCacheError when reading apk: " + path, e); - return DEX_OPT_FAILED; - } catch (Exception e) { - Slog.w(TAG, "Exception when doing dexopt : ", e); - return DEX_OPT_FAILED; } } } return DEX_OPT_SKIPPED; } - private String getAppInstructionSet(ApplicationInfo info) { - String instructionSet = getPreferredInstructionSet(); - - if (info.cpuAbi != null) { - instructionSet = VMRuntime.getInstructionSet(info.cpuAbi); + private static String[] getAppDexInstructionSets(ApplicationInfo info) { + if (info.primaryCpuAbi != null) { + if (info.secondaryCpuAbi != null) { + return new String[] { + VMRuntime.getInstructionSet(info.primaryCpuAbi), + VMRuntime.getInstructionSet(info.secondaryCpuAbi) }; + } else { + return new String[] { + VMRuntime.getInstructionSet(info.primaryCpuAbi) }; + } } - return instructionSet; + return new String[] { getPreferredInstructionSet() }; } - private String getAppInstructionSetFromSettings(PackageSetting ps) { - String instructionSet = getPreferredInstructionSet(); - - if (ps.cpuAbiString != null) { - instructionSet = VMRuntime.getInstructionSet(ps.cpuAbiString); + private static String[] getAppDexInstructionSets(PackageSetting ps) { + if (ps.primaryCpuAbiString != null) { + if (ps.secondaryCpuAbiString != null) { + return new String[] { ps.primaryCpuAbiString, ps.secondaryCpuAbiString }; + } else { + return new String[] { ps.primaryCpuAbiString }; + } } - return instructionSet; + return new String[] { getPreferredInstructionSet() }; } private static String getPreferredInstructionSet() { @@ -4726,7 +4727,7 @@ public class PackageManagerService extends IPackageManager.Stub { } else { done = null; } - return performDexOptLI(pkg, null /* instruction set override */, forceDex, defer, done); + return performDexOptLI(pkg, null /* target instruction sets */, forceDex, defer, done); } private boolean verifyPackageUpdateLPr(PackageSetting oldPkg, PackageParser.Package newPkg) { @@ -4790,12 +4791,6 @@ public class PackageManagerService extends IPackageManager.Stub { } } - final File nativeLibraryFile = new File(mAppLibInstallDir, packageName); - NativeLibraryHelper.removeNativeBinariesFromDirLI(nativeLibraryFile); - if (!nativeLibraryFile.delete()) { - Slog.w(TAG, "Couldn't delete native library directory " + nativeLibraryFile.getPath()); - } - return res; } @@ -4827,7 +4822,9 @@ public class PackageManagerService extends IPackageManager.Stub { // Fix that up here. if (isSystemApp(pkg)) { PackageSetting ps = mSettings.mPackages.get(pkg.applicationInfo.packageName); - setInternalAppNativeLibraryPath(pkg, ps); + if (!isUpdatedSystemApp(pkg)) { + setBundledAppAbisAndRoots(pkg, ps); + } } if (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null) { @@ -5064,8 +5061,9 @@ public class PackageManagerService extends IPackageManager.Stub { // Just create the setting, don't add it yet. For already existing packages // the PkgSetting exists already and doesn't have to be created. pkgSetting = mSettings.getPackageLPw(pkg, origPackage, realName, suid, destCodeFile, - destResourceFile, pkg.applicationInfo.nativeLibraryDir, - pkg.applicationInfo.cpuAbi, + destResourceFile, pkg.applicationInfo.legacyNativeLibraryDir, + pkg.applicationInfo.primaryCpuAbi, + pkg.applicationInfo.secondaryCpuAbi, pkg.applicationInfo.flags, user, false); if (pkgSetting == null) { Slog.w(TAG, "Creating application package " + pkg.packageName + " failed"); @@ -5289,6 +5287,7 @@ public class PackageManagerService extends IPackageManager.Stub { + pkg.applicationInfo.uid + "/fs_" + currentUid; pkg.applicationInfo.nativeLibraryDir = pkg.applicationInfo.dataDir; + pkg.applicationInfo.legacyNativeLibraryDir = pkg.applicationInfo.dataDir; String msg = "Package " + pkg.packageName + " has mismatched uid: " + currentUid + " on disk, " @@ -5320,6 +5319,7 @@ public class PackageManagerService extends IPackageManager.Stub { pkg.applicationInfo.seinfo); if (ret < 0) { // Error from installer + Slog.w(TAG, "Unable to create data dirs [errorCode=" + ret + "]"); mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; return null; } @@ -5332,158 +5332,185 @@ public class PackageManagerService extends IPackageManager.Stub { } } - /* - * Set the data dir to the default "/data/data/<package name>/lib" - * if we got here without anyone telling us different (e.g., apps - * stored on SD card have their native libraries stored in the ASEC - * container with the APK). - * - * This happens during an upgrade from a package settings file that - * doesn't have a native library path attribute at all. - */ - if (pkg.applicationInfo.nativeLibraryDir == null && pkg.applicationInfo.dataDir != null) { - if (pkgSetting.nativeLibraryPathString == null) { - setInternalAppNativeLibraryPath(pkg, pkgSetting); - } else { - pkg.applicationInfo.nativeLibraryDir = pkgSetting.nativeLibraryPathString; - } - } pkgSetting.uidError = uidError; } final String path = scanFile.getPath(); - /* Note: We don't want to unpack the native binaries for - * system applications, unless they have been updated - * (the binaries are already under /system/lib). - * Also, don't unpack libs for apps on the external card - * since they should have their libraries in the ASEC - * container already. - * - * In other words, we're going to unpack the binaries - * only for non-system apps and system app upgrades. - */ - if (pkg.applicationInfo.nativeLibraryDir != null) { + final String codePath = pkg.applicationInfo.getCodePath(); + if (isSystemApp(pkg) && !isUpdatedSystemApp(pkg)) { + // For the case where we had previously uninstalled an update, get rid + // of any native binaries we might have unpackaged. Note that this assumes + // that system app updates were not installed via ASEC. + // + // TODO(multiArch): Is this cleanup really necessary ? + NativeLibraryHelper.removeNativeBinariesFromDirLI( + new File(codePath, LIB_DIR_NAME), false /* delete dirs */); + setBundledAppAbisAndRoots(pkg, pkgSetting); + } else { + // TODO: We can probably be smarter about this stuff. For installed apps, + // we can calculate this information at install time once and for all. For + // system apps, we can probably assume that this information doesn't change + // after the first boot scan. As things stand, we do lots of unnecessary work. + + final boolean isAsec = isForwardLocked(pkg) || isExternal(pkg); + final String nativeLibraryRootStr; + final boolean useIsaSpecificSubdirs; + if (pkg.applicationInfo.legacyNativeLibraryDir != null) { + nativeLibraryRootStr = pkg.applicationInfo.legacyNativeLibraryDir; + useIsaSpecificSubdirs = false; + } else { + nativeLibraryRootStr = new File(pkg.codePath, LIB_DIR_NAME).getAbsolutePath(); + useIsaSpecificSubdirs = true; + } + NativeLibraryHelper.Handle handle = null; try { handle = NativeLibraryHelper.Handle.create(scanFile); - // 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 - // that don't have 64 bit support. - String[] abiList = Build.SUPPORTED_ABIS; - boolean hasLegacyRenderscriptBitcode = false; - if (abiOverride != null) { - abiList = new String[] { abiOverride }; - } else if (Build.SUPPORTED_64_BIT_ABIS.length > 0 && - NativeLibraryHelper.hasRenderscriptBitcode(handle)) { - abiList = Build.SUPPORTED_32_BIT_ABIS; - hasLegacyRenderscriptBitcode = true; - } - - File nativeLibraryDir = new File(pkg.applicationInfo.nativeLibraryDir); - final String dataPathString = dataPath.getCanonicalPath(); - - if (isSystemApp(pkg) && !isUpdatedSystemApp(pkg)) { - /* - * Upgrading from a previous version of the OS sometimes - * leaves native libraries in the /data/data/<app>/lib - * directory for system apps even when they shouldn't be. - * Recent changes in the JNI library search path - * necessitates we remove those to match previous behavior. - */ - if (NativeLibraryHelper.removeNativeBinariesFromDirLI(nativeLibraryDir)) { - Log.i(TAG, "removed obsolete native libraries for system package " - + path); + // TODO(multiArch): This can be null for apps that didn't go through the + // usual installation process. We can calculate it again, like we + // do during install time. + // + // TODO(multiArch): Why do we need to rescan ASEC apps again ? It seems totally + // unnecessary. + final File nativeLibraryRoot = new File(nativeLibraryRootStr); + + // Null out the abis so that they can be recalculated. + pkg.applicationInfo.primaryCpuAbi = null; + pkg.applicationInfo.secondaryCpuAbi = null; + if (isMultiArch(pkg.applicationInfo)) { + // Warn if we've set an abiOverride for multi-lib packages.. + // By definition, we need to copy both 32 and 64 bit libraries for + // such packages. + if (abiOverride != null) { + Slog.w(TAG, "Ignoring abiOverride for multi arch application."); + } + + int abi32 = PackageManager.NO_NATIVE_LIBRARIES; + int abi64 = PackageManager.NO_NATIVE_LIBRARIES; + if (Build.SUPPORTED_32_BIT_ABIS.length > 0) { + if (isAsec) { + abi32 = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_32_BIT_ABIS); + } else { + abi32 = copyNativeLibrariesForInternalApp(handle, + nativeLibraryRoot, Build.SUPPORTED_32_BIT_ABIS, useIsaSpecificSubdirs); + } } - if (abiOverride != null || hasLegacyRenderscriptBitcode) { - pkg.applicationInfo.cpuAbi = abiList[0]; - pkgSetting.cpuAbiString = abiList[0]; - } else { - setInternalAppAbi(pkg, pkgSetting); + + if (abi32 < 0 && abi32 != PackageManager.NO_NATIVE_LIBRARIES) { + Slog.w(TAG, "Error unpackaging 32 bit native libs for multiarch app, errorCode=" + abi32); + mLastScanError = PackageManager.INSTALL_FAILED_INTERNAL_ERROR; + return null; } - } else { - if (!isForwardLocked(pkg) && !isExternal(pkg)) { - /* - * Update native library dir if it starts with - * /data/data - */ - if (nativeLibraryDir.getPath().startsWith(dataPathString)) { - setInternalAppNativeLibraryPath(pkg, pkgSetting); - nativeLibraryDir = new File(pkg.applicationInfo.nativeLibraryDir); + + if (Build.SUPPORTED_64_BIT_ABIS.length > 0) { + if (isAsec) { + abi64 = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_64_BIT_ABIS); + } else { + abi64 = copyNativeLibrariesForInternalApp(handle, + nativeLibraryRoot, Build.SUPPORTED_64_BIT_ABIS, useIsaSpecificSubdirs); } + } - try { - int copyRet = copyNativeLibrariesForInternalApp(handle, - nativeLibraryDir, abiList); - if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) { - Slog.e(TAG, "Unable to copy native libraries"); - mLastScanError = PackageManager.INSTALL_FAILED_INTERNAL_ERROR; - return null; - } + if (abi64 < 0 && abi64 != PackageManager.NO_NATIVE_LIBRARIES) { + Slog.w(TAG, "Error unpackaging 64 bit native libs for multiarch app, errorCode=" + abi32); + mLastScanError = PackageManager.INSTALL_FAILED_INTERNAL_ERROR; + return null; + } - // We've successfully copied native libraries across, so we make a - // note of what ABI we're using - if (copyRet != PackageManager.NO_NATIVE_LIBRARIES) { - pkg.applicationInfo.cpuAbi = abiList[copyRet]; - } else if (abiOverride != null || hasLegacyRenderscriptBitcode) { - pkg.applicationInfo.cpuAbi = abiList[0]; - } else { - pkg.applicationInfo.cpuAbi = null; - } - } catch (IOException e) { - Slog.e(TAG, "Unable to copy native libraries", e); - mLastScanError = PackageManager.INSTALL_FAILED_INTERNAL_ERROR; - return null; - } - } else { - // We don't have to copy the shared libraries if we're in the ASEC container - // but we still need to scan the file to figure out what ABI the app needs. - // - // TODO: This duplicates work done in the default container service. It's possible - // to clean this up but we'll need to change the interface between this service - // and IMediaContainerService (but doing so will spread this logic out, rather - // than centralizing it). - final int abi = NativeLibraryHelper.findSupportedAbi(handle, abiList); - if (abi >= 0) { - pkg.applicationInfo.cpuAbi = abiList[abi]; - } else if (abi == PackageManager.NO_NATIVE_LIBRARIES) { - // Note that (non upgraded) system apps will not have any native - // libraries bundled in their APK, but we're guaranteed not to be - // such an app at this point. - if (abiOverride != null || hasLegacyRenderscriptBitcode) { - pkg.applicationInfo.cpuAbi = abiList[0]; - } else { - pkg.applicationInfo.cpuAbi = null; - } + + if (abi64 >= 0) { + pkg.applicationInfo.primaryCpuAbi = Build.SUPPORTED_64_BIT_ABIS[abi64]; + } + + if (abi32 >= 0) { + final String abi = Build.SUPPORTED_32_BIT_ABIS[abi32]; + if (abi64 >= 0) { + pkg.applicationInfo.secondaryCpuAbi = abi; } else { - mLastScanError = PackageManager.INSTALL_FAILED_INTERNAL_ERROR; - return null; + pkg.applicationInfo.primaryCpuAbi = abi; } } + } else { + String[] abiList = (abiOverride != null) ? + new String[] { abiOverride } : Build.SUPPORTED_ABIS; - if (DEBUG_INSTALL) Slog.i(TAG, "Linking native library dir for " + path); - final int[] userIds = sUserManager.getUserIds(); - synchronized (mInstallLock) { - for (int userId : userIds) { - if (mInstaller.linkNativeLibraryDirectory(pkg.packageName, - pkg.applicationInfo.nativeLibraryDir, userId) < 0) { - Slog.w(TAG, "Failed linking native library dir (user=" + userId - + ")"); - mLastScanError = PackageManager.INSTALL_FAILED_INTERNAL_ERROR; - return null; - } - } + // 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 + // that don't have 64 bit support. + if (Build.SUPPORTED_64_BIT_ABIS.length > 0 && abiOverride == null && + NativeLibraryHelper.hasRenderscriptBitcode(handle)) { + abiList = Build.SUPPORTED_32_BIT_ABIS; } - } - pkgSetting.cpuAbiString = pkg.applicationInfo.cpuAbi; + final int copyRet; + if (isAsec) { + copyRet = NativeLibraryHelper.findSupportedAbi(handle, abiList); + } else { + copyRet = copyNativeLibrariesForInternalApp(handle, nativeLibraryRoot, abiList, + useIsaSpecificSubdirs); + } + + if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) { + Slog.w(TAG, "Error unpackaging native libs for app, errorCode=" + copyRet); + mLastScanError = PackageManager.INSTALL_FAILED_INTERNAL_ERROR; + return null; + } + + if (copyRet >= 0) { + pkg.applicationInfo.primaryCpuAbi = abiList[copyRet]; + } + } } catch (IOException ioe) { Slog.e(TAG, "Unable to get canonical file " + ioe.toString()); } finally { IoUtils.closeQuietly(handle); } + + if (DEBUG_INSTALL) Slog.i(TAG, "Linking native library dir for " + path); + final int[] userIds = sUserManager.getUserIds(); + synchronized (mInstallLock) { + // Create a native library symlink only if we have native libraries + // and if the native libraries are 32 bit libraries. We do not provide + // this symlink for 64 bit libraries. + if (pkg.applicationInfo.primaryCpuAbi != null && + !VMRuntime.is64BitAbi(pkg.applicationInfo.primaryCpuAbi)) { + final String nativeLibPath; + if (pkg.applicationInfo.legacyNativeLibraryDir != null) { + nativeLibPath = pkg.applicationInfo.legacyNativeLibraryDir; + } else { + nativeLibPath = new File(nativeLibraryRootStr, + VMRuntime.getInstructionSet(pkg.applicationInfo.primaryCpuAbi)).getAbsolutePath(); + } + + for (int userId : userIds) { + if (mInstaller.linkNativeLibraryDirectory(pkg.packageName, nativeLibPath, userId) < 0) { + Slog.w(TAG, "Failed linking native library dir (user=" + userId + + ")"); + mLastScanError = PackageManager.INSTALL_FAILED_INTERNAL_ERROR; + return null; + } + } + } + } + + pkgSetting.primaryCpuAbiString = pkg.applicationInfo.primaryCpuAbi; + pkgSetting.secondaryCpuAbiString = pkg.applicationInfo.secondaryCpuAbi; } + if (DEBUG_ABI_SELECTION) { + Log.d(TAG, "Abis for package[" + pkg.packageName + "] are" + + " primary=" + pkg.applicationInfo.primaryCpuAbi + + " secondary=" + pkg.applicationInfo.secondaryCpuAbi); + } + + // Check if we have a legacy native library path, use it if we do. + pkg.applicationInfo.legacyNativeLibraryDir = pkgSetting.legacyNativeLibraryPathString; + + // Now that we've calculated the ABIs and determined if it's an internal app, + // we will go ahead and populate the nativeLibraryPath. + populateDefaultNativeLibraryPath(pkg, pkg.applicationInfo); + if ((scanMode&SCAN_BOOTING) == 0 && pkgSetting.sharedUser != null) { // We don't do this here during boot because we can do it all // at once after scanning all existing packages. @@ -5929,6 +5956,9 @@ public class PackageManagerService extends IPackageManager.Stub { a.info.splitSourceDirs = pkg.applicationInfo.splitSourceDirs; a.info.splitPublicSourceDirs = pkg.applicationInfo.splitPublicSourceDirs; a.info.dataDir = pkg.applicationInfo.dataDir; + + // TODO: Update instrumentation.nativeLibraryDir as well ? Does it + // need other information about the application, like the ABI and what not ? a.info.nativeLibraryDir = pkg.applicationInfo.nativeLibraryDir; mInstrumentation.put(a.getComponentName(), a); if ((parseFlags&PackageParser.PARSE_CHATTY) != 0) { @@ -5989,13 +6019,16 @@ public class PackageManagerService extends IPackageManager.Stub { * match {@code scannedPackage} or will update the ABI of {@code scannedPackage} to match * the ABI selected for {@code packagesForUser}. This variant is used when installing or * updating a package that belongs to a shared user. + * + * NOTE: We currently only match for the primary CPU abi string. Matching the secondary + * adds unnecessary complexity. */ private boolean adjustCpuAbisForSharedUserLPw(Set<PackageSetting> packagesForUser, PackageParser.Package scannedPackage, boolean forceDexOpt, boolean deferDexOpt) { String requiredInstructionSet = null; - if (scannedPackage != null && scannedPackage.applicationInfo.cpuAbi != null) { + if (scannedPackage != null && scannedPackage.applicationInfo.primaryCpuAbi != null) { requiredInstructionSet = VMRuntime.getInstructionSet( - scannedPackage.applicationInfo.cpuAbi); + scannedPackage.applicationInfo.primaryCpuAbi); } PackageSetting requirer = null; @@ -6005,11 +6038,11 @@ public class PackageManagerService extends IPackageManager.Stub { // we will never be able to change the ABI of any package belonging to a shared // user, even if it's compatible with other packages. if (scannedPackage == null || ! scannedPackage.packageName.equals(ps.name)) { - if (ps.cpuAbiString == null) { + if (ps.primaryCpuAbiString == null) { continue; } - final String instructionSet = VMRuntime.getInstructionSet(ps.cpuAbiString); + final String instructionSet = VMRuntime.getInstructionSet(ps.primaryCpuAbiString); if (requiredInstructionSet != null) { if (!instructionSet.equals(requiredInstructionSet)) { // We have a mismatch between instruction sets (say arm vs arm64). @@ -6037,30 +6070,30 @@ public class PackageManagerService extends IPackageManager.Stub { // requirer != null implies that either scannedPackage was null or that scannedPackage // did not require an ABI, in which case we have to adjust scannedPackage to match // the ABI of the set (which is the same as requirer's ABI) - adjustedAbi = requirer.cpuAbiString; + adjustedAbi = requirer.primaryCpuAbiString; if (scannedPackage != null) { - scannedPackage.applicationInfo.cpuAbi = adjustedAbi; + scannedPackage.applicationInfo.primaryCpuAbi = adjustedAbi; } } else { // requirer == null implies that we're updating all ABIs in the set to // match scannedPackage. - adjustedAbi = scannedPackage.applicationInfo.cpuAbi; + adjustedAbi = scannedPackage.applicationInfo.primaryCpuAbi; } for (PackageSetting ps : packagesForUser) { if (scannedPackage == null || !scannedPackage.packageName.equals(ps.name)) { - if (ps.cpuAbiString != null) { + if (ps.primaryCpuAbiString != null) { continue; } - ps.cpuAbiString = adjustedAbi; + ps.primaryCpuAbiString = adjustedAbi; if (ps.pkg != null && ps.pkg.applicationInfo != null) { - ps.pkg.applicationInfo.cpuAbi = adjustedAbi; + ps.pkg.applicationInfo.primaryCpuAbi = adjustedAbi; Slog.i(TAG, "Adjusting ABI for : " + ps.name + " to " + adjustedAbi); if (performDexOptLI(ps.pkg, forceDexOpt, deferDexOpt, true) == DEX_OPT_FAILED) { - ps.cpuAbiString = null; - ps.pkg.applicationInfo.cpuAbi = null; + ps.primaryCpuAbiString = null; + ps.pkg.applicationInfo.primaryCpuAbi = null; return false; } else { mInstaller.rmdex(ps.codePathString, getPreferredInstructionSet()); @@ -6097,7 +6130,7 @@ public class PackageManagerService extends IPackageManager.Stub { } } - private String calculateApkRoot(final String codePathString) { + private static String calculateApkRoot(final String codePathString) { final File codePath = new File(codePathString); final File codeRoot; if (FileUtils.contains(Environment.getRootDirectory(), codePath)) { @@ -6121,7 +6154,7 @@ public class PackageManagerService extends IPackageManager.Stub { Slog.w(TAG, "Unrecognized code path " + codePath + " - using " + codeRoot); } catch (IOException e) { - // Can't canonicalize the lib path -- shenanigans? + // Can't canonicalize the code path -- shenanigans? Slog.w(TAG, "Can't canonicalize code path " + codePath); return Environment.getRootDirectory().getPath(); } @@ -6129,104 +6162,154 @@ public class PackageManagerService extends IPackageManager.Stub { return codeRoot.getPath(); } - // This is the initial scan-time determination of how to handle a given - // package for purposes of native library location. - private void setInternalAppNativeLibraryPath(PackageParser.Package pkg, - PackageSetting pkgSetting) { - // "bundled" here means system-installed with no overriding update - final boolean bundledApk = isSystemApp(pkg) && !isUpdatedSystemApp(pkg); - final File codeFile = new File(pkg.applicationInfo.getCodePath()); - final String apkName = deriveCodePathName(pkg.applicationInfo.getCodePath()); - - String nativeLibraryPath = null; - if (bundledApk) { - // If "/system/lib64/apkname" exists, assume that is the per-package - // native library directory to use; otherwise use "/system/lib/apkname". - String apkRoot = calculateApkRoot(pkg.applicationInfo.getCodePath()); - File lib64 = new File(apkRoot, LIB64_DIR_NAME); - File packLib64 = new File(lib64, apkName); - File libDir = (packLib64.exists()) ? lib64 : new File(apkRoot, LIB_DIR_NAME); - nativeLibraryPath = (new File(libDir, apkName)).getAbsolutePath(); - } else if (isApkFile(codeFile)) { - // Monolithic install - nativeLibraryPath = (new File(mAppLibInstallDir, apkName)).getAbsolutePath(); - } else { - // Cluster install - // TODO: pipe through abiOverride - String[] abiList = Build.SUPPORTED_ABIS; - NativeLibraryHelper.Handle handle = null; - try { - handle = NativeLibraryHelper.Handle.create(codeFile); - if (Build.SUPPORTED_64_BIT_ABIS.length > 0 && - NativeLibraryHelper.hasRenderscriptBitcode(handle)) { - abiList = Build.SUPPORTED_32_BIT_ABIS; + private void populateDefaultNativeLibraryPath(PackageParser.Package pkg, + ApplicationInfo info) { + if (info.legacyNativeLibraryDir != null) { + // Not a cluster install. + if (DEBUG_ABI_SELECTION) { + Log.i(TAG, "Set nativeLibraryDir [non_cluster] for: " + pkg.packageName + + " to " + info.legacyNativeLibraryDir); + } + info.nativeLibraryDir = info.legacyNativeLibraryDir; + } else if (info.primaryCpuAbi != null) { + final boolean is64Bit = VMRuntime.is64BitAbi(info.primaryCpuAbi); + if (info.apkRoot != null) { + // This is a bundled system app so choose the path based on the ABI. + // if it's a 64 bit abi, use lib64 otherwise use lib32. Note that this + // is just the default path. + final String apkName = deriveCodePathName(pkg.applicationInfo.getCodePath()); + final String libDir = is64Bit ? LIB64_DIR_NAME : LIB_DIR_NAME; + info.nativeLibraryDir = (new File(info.apkRoot, new File(libDir, apkName).getAbsolutePath())) + .getAbsolutePath(); + + if (DEBUG_ABI_SELECTION) { + Log.i(TAG, "Set nativeLibraryDir [system] for: " + pkg.packageName + + " to " + info.nativeLibraryDir); } + } else { + // Cluster install. legacyNativeLibraryDir == null && primaryCpuAbi = null + // implies this must be a cluster package. + final String codePath = pkg.codePath; + final File libPath = new File(new File(codePath, LIB_DIR_NAME), + VMRuntime.getInstructionSet(info.primaryCpuAbi)); + info.nativeLibraryDir = libPath.getAbsolutePath(); - final int abiIndex = NativeLibraryHelper.findSupportedAbi(handle, abiList); - if (abiIndex >= 0) { - final File baseLibFile = new File(codeFile, LIB_DIR_NAME); - final String abi = Build.SUPPORTED_ABIS[abiIndex]; - final String instructionSet = VMRuntime.getInstructionSet(abi); - nativeLibraryPath = new File(baseLibFile, instructionSet).getAbsolutePath(); + if (DEBUG_ABI_SELECTION) { + Log.i(TAG, "Set nativeLibraryDir [cluster] for: " + pkg.packageName + + " to " + info.nativeLibraryDir); } - } catch (IOException e) { - Slog.e(TAG, "Failed to detect native libraries", e); - } finally { - IoUtils.closeQuietly(handle); } + } else { + if (DEBUG_ABI_SELECTION) { + Log.i(TAG, "Setting nativeLibraryDir to null for: " + pkg.packageName); + } + + info.nativeLibraryDir = null; } - pkg.applicationInfo.nativeLibraryDir = nativeLibraryPath; + } + + /** + * Calculate the abis and roots for a bundled app. These can uniquely + * be determined from the contents of the system partition, i.e whether + * it contains 64 or 32 bit shared libraries etc. We do not validate any + * of this information, and instead assume that the system was built + * sensibly. + */ + private void setBundledAppAbisAndRoots(PackageParser.Package pkg, + PackageSetting pkgSetting) { + final String apkName = deriveCodePathName(pkg.applicationInfo.getCodePath()); + + // If "/system/lib64/apkname" exists, assume that is the per-package + // native library directory to use; otherwise use "/system/lib/apkname". + final String apkRoot = calculateApkRoot(pkg.applicationInfo.sourceDir); + pkg.applicationInfo.apkRoot = apkRoot; + setBundledAppAbi(pkg, apkRoot, apkName); // pkgSetting might be null during rescan following uninstall of updates // to a bundled app, so accommodate that possibility. The settings in // that case will be established later from the parsed package. + // + // If the settings aren't null, sync them up with what we've just derived. + // note that apkRoot isn't stored in the package settings. if (pkgSetting != null) { - pkgSetting.nativeLibraryPathString = nativeLibraryPath; + pkgSetting.primaryCpuAbiString = pkg.applicationInfo.primaryCpuAbi; + pkgSetting.secondaryCpuAbiString = pkg.applicationInfo.secondaryCpuAbi; } } - // Deduces the required ABI of an upgraded system app. - private void setInternalAppAbi(PackageParser.Package pkg, PackageSetting pkgSetting) { - final String apkRoot = calculateApkRoot(pkg.applicationInfo.getCodePath()); - final String apkName = deriveCodePathName(pkg.applicationInfo.getCodePath()); - + /** + * Deduces the ABI of a bundled app and sets the relevant fields on the + * parsed pkg object. + * + * @param apkRoot the root of the installed apk, something like {@code /system} or {@code /oem} + * under which system libraries are installed. + * @param apkName the name of the installed package. + */ + private static void setBundledAppAbi(PackageParser.Package pkg, String apkRoot, String apkName) { // This is of the form "/system/lib64/<packagename>", "/vendor/lib64/<packagename>" // or similar. - final File lib64 = new File(apkRoot, new File(LIB64_DIR_NAME, apkName).getPath()); - final File lib = new File(apkRoot, new File(LIB_DIR_NAME, apkName).getPath()); - - // Assume that the bundled native libraries always correspond to the - // most preferred 32 or 64 bit ABI. - if (lib64.exists()) { - pkg.applicationInfo.cpuAbi = Build.SUPPORTED_64_BIT_ABIS[0]; - pkgSetting.cpuAbiString = Build.SUPPORTED_64_BIT_ABIS[0]; - } else if (lib.exists()) { - pkg.applicationInfo.cpuAbi = Build.SUPPORTED_32_BIT_ABIS[0]; - pkgSetting.cpuAbiString = Build.SUPPORTED_32_BIT_ABIS[0]; + final boolean has64BitLibs = (new File(apkRoot, new File(LIB64_DIR_NAME, apkName).getPath())).exists(); + final boolean has32BitLibs = (new File(apkRoot, new File(LIB_DIR_NAME, apkName).getPath())).exists(); + + if (has64BitLibs && !has32BitLibs) { + // The package has 64 bit libs, but not 32 bit libs. Its primary + // ABI should be 64 bit. We can safely assume here that the bundled + // native libraries correspond to the most preferred ABI in the list. + + pkg.applicationInfo.primaryCpuAbi = Build.SUPPORTED_64_BIT_ABIS[0]; + pkg.applicationInfo.secondaryCpuAbi = null; + } else if (has32BitLibs && !has64BitLibs) { + // The package has 32 bit libs but not 64 bit libs. Its primary + // ABI should be 32 bit. + + pkg.applicationInfo.primaryCpuAbi = Build.SUPPORTED_32_BIT_ABIS[0]; + pkg.applicationInfo.secondaryCpuAbi = null; + } else if (has32BitLibs && has64BitLibs) { + // The application has both 64 and 32 bit bundled libraries. We check + // here that the app declares multiArch support, and warn if it doesn't. + // + // We will be lenient here and record both ABIs. The primary will be the + // ABI that's higher on the list, i.e, a device that's configured to prefer + // 64 bit apps will see a 64 bit primary ABI, + + if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_MULTIARCH) == 0) { + Slog.e(TAG, "Package: " + pkg + " has multiple bundled libs, but is not multiarch."); + } + + if (VMRuntime.is64BitAbi(getPreferredInstructionSet())) { + pkg.applicationInfo.primaryCpuAbi = Build.SUPPORTED_64_BIT_ABIS[0]; + pkg.applicationInfo.secondaryCpuAbi = Build.SUPPORTED_32_BIT_ABIS[0]; + } else { + pkg.applicationInfo.primaryCpuAbi = Build.SUPPORTED_32_BIT_ABIS[0]; + pkg.applicationInfo.secondaryCpuAbi = Build.SUPPORTED_64_BIT_ABIS[0]; + } } else { - // This is the case where the app has no native code. - pkg.applicationInfo.cpuAbi = null; - pkgSetting.cpuAbiString = null; + pkg.applicationInfo.primaryCpuAbi = null; + pkg.applicationInfo.secondaryCpuAbi = null; } } - private static int copyNativeLibrariesForInternalApp(NativeLibraryHelper.Handle handle, - final File nativeLibraryDir, String[] abiList) throws IOException { - if (!nativeLibraryDir.isDirectory()) { - nativeLibraryDir.delete(); + private static void createNativeLibrarySubdir(File path) throws IOException { + if (!path.isDirectory()) { + path.delete(); - if (!nativeLibraryDir.mkdir()) { - throw new IOException("Cannot create " + nativeLibraryDir.getPath()); + if (!path.mkdir()) { + throw new IOException("Cannot create " + path.getPath()); } try { - Os.chmod(nativeLibraryDir.getPath(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); + Os.chmod(path.getPath(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); } catch (ErrnoException e) { throw new IOException("Cannot chmod native library directory " - + nativeLibraryDir.getPath(), e); + + path.getPath(), e); } - } else if (!SELinux.restorecon(nativeLibraryDir)) { - throw new IOException("Cannot set SELinux context for " + nativeLibraryDir.getPath()); + } else if (!SELinux.restorecon(path)) { + throw new IOException("Cannot set SELinux context for " + path.getPath()); } + } + + private static int copyNativeLibrariesForInternalApp(NativeLibraryHelper.Handle handle, + final File nativeLibraryRoot, String[] abiList, boolean useIsaSubdir) throws IOException { + createNativeLibrarySubdir(nativeLibraryRoot); /* * If this is an internal application or our nativeLibraryPath points to @@ -6234,8 +6317,22 @@ public class PackageManagerService extends IPackageManager.Stub { */ int abi = NativeLibraryHelper.findSupportedAbi(handle, abiList); if (abi >= 0) { + /* + * If we have a matching instruction set, construct a subdir under the native + * library root that corresponds to this instruction set. + */ + final String instructionSet = VMRuntime.getInstructionSet(abiList[abi]); + final File subDir; + if (useIsaSubdir) { + final File isaSubdir = new File(nativeLibraryRoot, instructionSet); + createNativeLibrarySubdir(isaSubdir); + subDir = isaSubdir; + } else { + subDir = nativeLibraryRoot; + } + int copyRet = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle, - nativeLibraryDir, Build.SUPPORTED_ABIS[abi]); + subDir, Build.SUPPORTED_ABIS[abi]); if (copyRet != PackageManager.INSTALL_SUCCEEDED) { return copyRet; } @@ -8374,7 +8471,7 @@ public class PackageManagerService extends IPackageManager.Stub { private InstallArgs mArgs; private int mRet; final String packageAbiOverride; - final String packageInstructionSetOverride; + boolean multiArch; InstallParams(File originFile, boolean originStaged, IPackageInstallObserver2 observer, int flags, String installerPackageName, VerificationParams verificationParams, @@ -8387,8 +8484,6 @@ public class PackageManagerService extends IPackageManager.Stub { this.installerPackageName = installerPackageName; this.verificationParams = verificationParams; this.packageAbiOverride = packageAbiOverride; - this.packageInstructionSetOverride = (packageAbiOverride == null) ? - packageAbiOverride : VMRuntime.getInstructionSet(packageAbiOverride); } @Override @@ -8499,6 +8594,11 @@ public class PackageManagerService extends IPackageManager.Stub { final String originPath = originFile.getAbsolutePath(); pkgLite = mContainerService.getMinimalPackageInfo(originPath, flags, lowThreshold, packageAbiOverride); + // Keep track of whether this package is a multiArch package until + // we perform a full scan of it. We need to do this because we might + // end up extracting the package shared libraries before we perform + // a full scan. + multiArch = pkgLite.multiArch; /* * If we have too little free space, try to free cache @@ -8744,7 +8844,8 @@ public class PackageManagerService extends IPackageManager.Stub { int mRet; MoveParams(InstallArgs srcArgs, IPackageMoveObserver observer, int flags, - String packageName, String instructionSet, int uid, UserHandle user) { + String packageName, String[] instructionSets, int uid, UserHandle user, + boolean isMultiArch) { super(user); this.srcArgs = srcArgs; this.observer = observer; @@ -8754,7 +8855,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (srcArgs != null) { final String codePath = srcArgs.getCodePath(); targetArgs = createInstallArgsForMoveTarget(codePath, flags, packageName, - instructionSet); + instructionSets, isMultiArch); } else { targetArgs = null; } @@ -8869,7 +8970,8 @@ public class PackageManagerService extends IPackageManager.Stub { * when cleaning up old installs, or used as a move source. */ private InstallArgs createInstallArgsForExisting(int flags, String codePath, - String resourcePath, String nativeLibraryPath, String instructionSet) { + String resourcePath, String nativeLibraryRoot, String[] instructionSets, + boolean isMultiArch) { final boolean isInAsec; if (installOnSd(flags)) { /* Apps on SD card are always in ASEC containers. */ @@ -8886,23 +8988,24 @@ public class PackageManagerService extends IPackageManager.Stub { } if (isInAsec) { - return new AsecInstallArgs(codePath, resourcePath, nativeLibraryPath, - instructionSet, installOnSd(flags), installForwardLocked(flags)); + return new AsecInstallArgs(codePath, instructionSets, + installOnSd(flags), installForwardLocked(flags), isMultiArch); } else { - return new FileInstallArgs(codePath, resourcePath, nativeLibraryPath, instructionSet); + return new FileInstallArgs(codePath, resourcePath, nativeLibraryRoot, + instructionSets, isMultiArch); } } private InstallArgs createInstallArgsForMoveTarget(String codePath, int flags, String pkgName, - String instructionSet) { + String[] instructionSets, boolean isMultiArch) { final File codeFile = new File(codePath); if (installOnSd(flags) || installForwardLocked(flags)) { String cid = getNextCodePath(codePath, pkgName, "/" + AsecInstallArgs.RES_FILE_NAME); - return new AsecInstallArgs(codeFile, cid, instructionSet, installOnSd(flags), - installForwardLocked(flags)); + return new AsecInstallArgs(codeFile, cid, instructionSets, installOnSd(flags), + installForwardLocked(flags), isMultiArch); } else { - return new FileInstallArgs(codeFile, instructionSet); + return new FileInstallArgs(codeFile, instructionSets, isMultiArch); } } @@ -8920,12 +9023,18 @@ public class PackageManagerService extends IPackageManager.Stub { final String installerPackageName; final ManifestDigest manifestDigest; final UserHandle user; - final String instructionSet; final String abiOverride; + final boolean multiArch; + + // The list of instruction sets supported by this app. This is currently + // only used during the rmdex() phase to clean up resources. We can get rid of this + // if we move dex files under the common app path. + /* nullable */ String[] instructionSets; InstallArgs(File originFile, boolean originStaged, IPackageInstallObserver2 observer, - int flags, String installerPackageName, ManifestDigest manifestDigest, - UserHandle user, String instructionSet, String abiOverride) { + int flags, String installerPackageName, ManifestDigest manifestDigest, + UserHandle user, String[] instructionSets, + String abiOverride, boolean multiArch) { this.originFile = originFile; this.originStaged = originStaged; this.flags = flags; @@ -8933,8 +9042,9 @@ public class PackageManagerService extends IPackageManager.Stub { this.installerPackageName = installerPackageName; this.manifestDigest = manifestDigest; this.user = user; - this.instructionSet = instructionSet; + this.instructionSets = instructionSets; this.abiOverride = abiOverride; + this.multiArch = multiArch; } abstract int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException; @@ -8951,8 +9061,7 @@ public class PackageManagerService extends IPackageManager.Stub { abstract String getCodePath(); /** @see PackageSettingBase#resourcePathString */ abstract String getResourcePath(); - /** @see PackageSettingBase#nativeLibraryPathString */ - abstract String getNativeLibraryPath(); + abstract String getLegacyNativeLibraryPath(); // Need installer lock especially for dex file removal. abstract void cleanUpResourcesLI(); @@ -8995,7 +9104,7 @@ public class PackageManagerService extends IPackageManager.Stub { class FileInstallArgs extends InstallArgs { private File codeFile; private File resourceFile; - private File nativeLibraryFile; + private File legacyNativeLibraryPath; // Example topology: // /data/app/com.example/base.apk @@ -9008,24 +9117,27 @@ public class PackageManagerService extends IPackageManager.Stub { FileInstallArgs(InstallParams params) { super(params.originFile, params.originStaged, params.observer, params.flags, params.installerPackageName, params.getManifestDigest(), params.getUser(), - params.packageInstructionSetOverride, params.packageAbiOverride); + null /* instruction sets */, params.packageAbiOverride, + params.multiArch); if (isFwdLocked()) { throw new IllegalArgumentException("Forward locking only supported in ASEC"); } } /** Existing install */ - FileInstallArgs(String codePath, String resourcePath, String nativeLibraryPath, - String instructionSet) { - super(null, false, null, 0, null, null, null, instructionSet, null); + FileInstallArgs(String codePath, String resourcePath, String legacyNativeLibraryRoot, + String[] instructionSets, boolean isMultiArch) { + super(null, false, null, 0, null, null, null, instructionSets, null, isMultiArch); this.codeFile = (codePath != null) ? new File(codePath) : null; this.resourceFile = (resourcePath != null) ? new File(resourcePath) : null; - this.nativeLibraryFile = (nativeLibraryPath != null) ? new File(nativeLibraryPath) : null; + this.legacyNativeLibraryPath = (legacyNativeLibraryRoot != null) ? + new File(legacyNativeLibraryRoot) : null; } /** New install from existing */ - FileInstallArgs(File originFile, String instructionSet) { - super(originFile, false, null, 0, null, null, null, instructionSet, null); + FileInstallArgs(File originFile, String[] instructionSets, boolean isMultiArch) { + super(originFile, false, null, 0, null, null, null, instructionSets, null, + isMultiArch); } boolean checkFreeStorage(IMediaContainerService imcs) throws RemoteException { @@ -9091,35 +9203,67 @@ public class PackageManagerService extends IPackageManager.Stub { } } - String[] abiList = (abiOverride != null) ? - new String[] { abiOverride } : Build.SUPPORTED_ABIS; + final File libraryRoot = new File(codeFile, LIB_DIR_NAME); NativeLibraryHelper.Handle handle = null; try { handle = NativeLibraryHelper.Handle.create(codeFile); - if (Build.SUPPORTED_64_BIT_ABIS.length > 0 && - abiOverride == null && - NativeLibraryHelper.hasRenderscriptBitcode(handle)) { - abiList = Build.SUPPORTED_32_BIT_ABIS; - } - - // TODO: refactor to avoid double findSupportedAbi() - final int abiIndex = NativeLibraryHelper.findSupportedAbi(handle, abiList); - if (abiIndex < 0 && abiIndex != PackageManager.NO_NATIVE_LIBRARIES) { - return abiIndex; - } else if (abiIndex >= 0) { - final File baseLibFile = new File(codeFile, LIB_DIR_NAME); - baseLibFile.mkdir(); - Os.chmod(baseLibFile.getAbsolutePath(), 0755); - - final String abi = Build.SUPPORTED_ABIS[abiIndex]; - final String instructionSet = VMRuntime.getInstructionSet(abi); - nativeLibraryFile = new File(baseLibFile, instructionSet); - nativeLibraryFile.mkdir(); - Os.chmod(nativeLibraryFile.getAbsolutePath(), 0755); - - copyNativeLibrariesForInternalApp(handle, nativeLibraryFile, abiList); - } - } catch (IOException | ErrnoException e) { + if (multiArch) { + // Warn if we've set an abiOverride for multi-lib packages.. + // By definition, we need to copy both 32 and 64 bit libraries for + // such packages. + if (abiOverride != null) { + Slog.w(TAG, "Ignoring abiOverride for multi arch application."); + } + + int copyRet = PackageManager.NO_NATIVE_LIBRARIES; + if (Build.SUPPORTED_32_BIT_ABIS.length > 0) { + copyRet = copyNativeLibrariesForInternalApp(handle, libraryRoot, + Build.SUPPORTED_32_BIT_ABIS, true /* use isa specific subdirs */); + if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) { + Slog.w(TAG, "Failure copying 32 bit native libraries [errorCode=" + copyRet + "]"); + return copyRet; + } + } + + if (DEBUG_ABI_SELECTION && copyRet >= 0) { + Log.d(TAG, "Installed 32 bit libraries under: " + codeFile + " abi=" + + Build.SUPPORTED_32_BIT_ABIS[copyRet]); + } + + if (Build.SUPPORTED_64_BIT_ABIS.length > 0) { + copyRet = copyNativeLibrariesForInternalApp(handle, libraryRoot, + Build.SUPPORTED_64_BIT_ABIS, true /* use isa specific subdirs */); + if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) { + Slog.w(TAG, "Failure copying 64 bit native libraries [errorCode=" + copyRet + "]"); + return copyRet; + } + } + + if (DEBUG_ABI_SELECTION && copyRet >= 0) { + Log.d(TAG, "Installed 64 bit libraries under: " + codeFile + " abi=" + + Build.SUPPORTED_64_BIT_ABIS[copyRet]); + } + } else { + String[] abiList = (abiOverride != null) ? + new String[] { abiOverride } : Build.SUPPORTED_ABIS; + + if (Build.SUPPORTED_64_BIT_ABIS.length > 0 && abiOverride == null && + NativeLibraryHelper.hasRenderscriptBitcode(handle)) { + abiList = Build.SUPPORTED_32_BIT_ABIS; + } + + int copyRet = copyNativeLibrariesForInternalApp(handle, libraryRoot, abiList, + true /* use isa specific subdirs */); + if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) { + Slog.w(TAG, "Failure copying native libraries [errorCode=" + copyRet + "]"); + return copyRet; + } + + if (DEBUG_ABI_SELECTION && copyRet >= 0) { + Log.d(TAG, "Installed libraries under: " + codeFile + " abi=" + abiList[copyRet]); + } + } + } catch (IOException e) { Slog.e(TAG, "Copying native libraries failed", e); ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR; } finally { @@ -9156,8 +9300,6 @@ public class PackageManagerService extends IPackageManager.Stub { // Reflect the rename internally codeFile = afterCodeFile; resourceFile = afterCodeFile; - nativeLibraryFile = FileUtils.rewriteAfterRename(beforeCodeFile, afterCodeFile, - nativeLibraryFile); // Reflect the rename in scanned details pkg.codePath = afterCodeFile.getAbsolutePath(); @@ -9173,7 +9315,8 @@ public class PackageManagerService extends IPackageManager.Stub { pkg.applicationInfo.setResourcePath(pkg.codePath); pkg.applicationInfo.setBaseResourcePath(pkg.baseCodePath); pkg.applicationInfo.setSplitResourcePaths(pkg.splitCodePaths); - pkg.applicationInfo.nativeLibraryDir = getNativeLibraryPath(); + // Null out the legacy native dir so we stop using it. + pkg.applicationInfo.legacyNativeLibraryDir = null; return true; } @@ -9197,8 +9340,8 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override - String getNativeLibraryPath() { - return (nativeLibraryFile != null) ? nativeLibraryFile.getAbsolutePath() : null; + String getLegacyNativeLibraryPath() { + return (legacyNativeLibraryPath != null) ? legacyNativeLibraryPath.getAbsolutePath() : null; } private boolean cleanUp() { @@ -9215,9 +9358,11 @@ public class PackageManagerService extends IPackageManager.Stub { resourceFile.delete(); } - if (nativeLibraryFile != null && !FileUtils.contains(codeFile, nativeLibraryFile)) { - FileUtils.deleteContents(nativeLibraryFile); - nativeLibraryFile.delete(); + if (legacyNativeLibraryPath != null && !FileUtils.contains(codeFile, legacyNativeLibraryPath)) { + if (!FileUtils.deleteContents(legacyNativeLibraryPath)) { + Slog.w(TAG, "Couldn't delete native library directory " + legacyNativeLibraryPath); + } + legacyNativeLibraryPath.delete(); } return true; @@ -9238,16 +9383,18 @@ public class PackageManagerService extends IPackageManager.Stub { cleanUp(); if (!allCodePaths.isEmpty()) { - if (instructionSet == null) { + if (instructionSets == null) { throw new IllegalStateException("instructionSet == null"); } for (String codePath : allCodePaths) { - int retCode = mInstaller.rmdex(codePath, instructionSet); - if (retCode < 0) { - Slog.w(TAG, "Couldn't remove dex file for package: " - + " at location " + codePath + ", retcode=" + retCode); - // we don't consider this to be a failure of the core package deletion + for (String instructionSet : instructionSets) { + int retCode = mInstaller.rmdex(codePath, instructionSet); + if (retCode < 0) { + Slog.w(TAG, "Couldn't remove dex file for package: " + + " at location " + codePath + ", retcode=" + retCode); + // we don't consider this to be a failure of the core package deletion + } } } } @@ -9289,21 +9436,22 @@ public class PackageManagerService extends IPackageManager.Stub { String cid; String packagePath; String resourcePath; - String libraryPath; + String legacyNativeLibraryDir; /** New install */ AsecInstallArgs(InstallParams params) { super(params.originFile, params.originStaged, params.observer, params.flags, - params.installerPackageName, params.getManifestDigest(), params.getUser(), - params.packageInstructionSetOverride, params.packageAbiOverride); + params.installerPackageName, params.getManifestDigest(), + params.getUser(), null /* instruction sets */, + params.packageAbiOverride, params.multiArch); } /** Existing install */ - AsecInstallArgs(String fullCodePath, String fullResourcePath, String nativeLibraryPath, - String instructionSet, boolean isExternal, boolean isForwardLocked) { + AsecInstallArgs(String fullCodePath, String[] instructionSets, + boolean isExternal, boolean isForwardLocked, boolean isMultiArch) { super(null, false, null, (isExternal ? INSTALL_EXTERNAL : 0) | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null, - instructionSet, null); + instructionSets, null, isMultiArch); // Extract cid from fullCodePath int eidx = fullCodePath.lastIndexOf("/"); String subStr1 = fullCodePath.substring(0, eidx); @@ -9312,20 +9460,21 @@ public class PackageManagerService extends IPackageManager.Stub { setCachePath(subStr1); } - AsecInstallArgs(String cid, String instructionSet, boolean isForwardLocked) { + AsecInstallArgs(String cid, String[] instructionSets, boolean isForwardLocked, + boolean isMultiArch) { super(null, false, null, (isAsecExternal(cid) ? INSTALL_EXTERNAL : 0) | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null, - instructionSet, null); + instructionSets, null, isMultiArch); this.cid = cid; setCachePath(PackageHelper.getSdDir(cid)); } /** New install from existing */ - AsecInstallArgs(File originPackageFile, String cid, String instructionSet, - boolean isExternal, boolean isForwardLocked) { + AsecInstallArgs(File originPackageFile, String cid, String[] instructionSets, + boolean isExternal, boolean isForwardLocked, boolean isMultiArch) { super(originPackageFile, false, null, (isExternal ? INSTALL_EXTERNAL : 0) | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null, - instructionSet, null); + instructionSets, null, isMultiArch); this.cid = cid; } @@ -9376,8 +9525,8 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override - String getNativeLibraryPath() { - return libraryPath; + String getLegacyNativeLibraryPath() { + return legacyNativeLibraryDir; } int doPreInstall(int status) { @@ -9452,14 +9601,16 @@ public class PackageManagerService extends IPackageManager.Stub { pkg.applicationInfo.setResourcePath(getResourcePath()); pkg.applicationInfo.setBaseResourcePath(getResourcePath()); pkg.applicationInfo.setSplitResourcePaths(null); - pkg.applicationInfo.nativeLibraryDir = getNativeLibraryPath(); + // ASEC installs are considered "legacy" because we don't support + // multiarch on them yet, and use the old style paths on them. + pkg.applicationInfo.legacyNativeLibraryDir = legacyNativeLibraryDir; return true; } private void setCachePath(String newCachePath) { File cachePath = new File(newCachePath); - libraryPath = new File(cachePath, LIB_DIR_NAME).getPath(); + legacyNativeLibraryDir = new File(cachePath, LIB_DIR_NAME).getPath(); packagePath = new File(cachePath, RES_FILE_NAME).getPath(); if (isFwdLocked()) { @@ -9508,15 +9659,17 @@ public class PackageManagerService extends IPackageManager.Stub { void cleanUpResourcesLI() { String sourceFile = getCodePath(); // Remove dex file - if (instructionSet == null) { + if (instructionSets == null) { throw new IllegalStateException("instructionSet == null"); } - int retCode = mInstaller.rmdex(sourceFile, instructionSet); - if (retCode < 0) { - Slog.w(TAG, "Couldn't remove dex file for package: " - + " at location " - + sourceFile.toString() + ", retcode=" + retCode); - // we don't consider this to be a failure of the core package deletion + for (String instructionSet : instructionSets) { + int retCode = mInstaller.rmdex(sourceFile, instructionSet); + if (retCode < 0) { + Slog.w(TAG, "Couldn't remove dex file for package: " + + " at location " + + sourceFile.toString() + ", retcode=" + retCode); + // we don't consider this to be a failure of the core package deletion + } } cleanUp(); } @@ -9922,8 +10075,9 @@ public class PackageManagerService extends IPackageManager.Stub { res.removedInfo.args = createInstallArgsForExisting(0, deletedPackage.applicationInfo.getCodePath(), deletedPackage.applicationInfo.getResourcePath(), - deletedPackage.applicationInfo.nativeLibraryDir, - getAppInstructionSet(deletedPackage.applicationInfo)); + deletedPackage.applicationInfo.legacyNativeLibraryDir, + getAppDexInstructionSets(deletedPackage.applicationInfo), + isMultiArch(deletedPackage.applicationInfo)); } else { res.removedInfo.args = null; } @@ -9983,10 +10137,11 @@ public class PackageManagerService extends IPackageManager.Stub { private int moveDexFilesLI(String oldCodePath, PackageParser.Package newPackage) { // TODO: extend to move split APK dex files if ((newPackage.applicationInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0) { - final String instructionSet = getAppInstructionSet(newPackage.applicationInfo); - int retCode = mInstaller.movedex(oldCodePath, newPackage.baseCodePath, - instructionSet); - if (retCode != 0) { + final String[] instructionSets = getAppDexInstructionSets(newPackage.applicationInfo); + for (String instructionSet : instructionSets) { + int retCode = mInstaller.movedex(oldCodePath, newPackage.baseCodePath, + instructionSet); + if (retCode != 0) { /* * Programs may be lazily run through dexopt, so the * source may not exist. However, something seems to @@ -9995,9 +10150,10 @@ public class PackageManagerService extends IPackageManager.Stub { * remove the target to make sure there isn't a stale * file from a previous version of the package. */ - newPackage.mDexOptNeeded = true; - mInstaller.rmdex(oldCodePath, instructionSet); - mInstaller.rmdex(newPackage.baseCodePath, instructionSet); + newPackage.mDexOptNeeded = true; + mInstaller.rmdex(oldCodePath, instructionSet); + mInstaller.rmdex(newPackage.baseCodePath, instructionSet); + } } } return PackageManager.INSTALL_SUCCEEDED; @@ -10234,6 +10390,14 @@ public class PackageManagerService extends IPackageManager.Stub { return (ps.pkgFlags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0; } + private static boolean isMultiArch(PackageSetting ps) { + return (ps.pkgFlags & ApplicationInfo.FLAG_MULTIARCH) != 0; + } + + private static boolean isMultiArch(ApplicationInfo info) { + return (info.flags & ApplicationInfo.FLAG_MULTIARCH) != 0; + } + private static boolean isExternal(PackageParser.Package pkg) { return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0; } @@ -10591,7 +10755,7 @@ public class PackageManagerService extends IPackageManager.Stub { // Reinstate the old system package mSettings.enableSystemPackageLPw(newPs.name); // Remove any native libraries from the upgraded package. - NativeLibraryHelper.removeNativeBinariesLI(newPs.nativeLibraryPathString); + NativeLibraryHelper.removeNativeBinariesLI(newPs.legacyNativeLibraryPathString); } // Install the system package if (DEBUG_REMOVE) Slog.d(TAG, "Re-installing system package: " + disabledPs); @@ -10610,7 +10774,7 @@ public class PackageManagerService extends IPackageManager.Stub { // writer synchronized (mPackages) { PackageSetting ps = mSettings.mPackages.get(newPkg.packageName); - setInternalAppNativeLibraryPath(newPkg, ps); + setBundledAppAbisAndRoots(newPkg, ps); updatePermissionsLPw(newPkg.packageName, newPkg, UPDATE_PERMISSIONS_ALL | UPDATE_PERMISSIONS_REPLACE_PKG); if (applyUserRestrictions) { @@ -10650,8 +10814,8 @@ public class PackageManagerService extends IPackageManager.Stub { // Delete application code and resources if (deleteCodeAndResources && (outInfo != null)) { outInfo.args = createInstallArgsForExisting(packageFlagsToInstallFlags(ps), - ps.codePathString, ps.resourcePathString, ps.nativeLibraryPathString, - getAppInstructionSetFromSettings(ps)); + ps.codePathString, ps.resourcePathString, ps.legacyNativeLibraryPathString, + getAppDexInstructionSets(ps), isMultiArch(ps)); } return true; } @@ -11056,7 +11220,7 @@ public class PackageManagerService extends IPackageManager.Stub { } PackageParser.Package p; boolean dataOnly = false; - String libDirPath = null; + String libDirRoot = null; String asecPath = null; PackageSetting ps = null; synchronized (mPackages) { @@ -11071,7 +11235,7 @@ public class PackageManagerService extends IPackageManager.Stub { p = ps.pkg; } if (ps != null) { - libDirPath = ps.nativeLibraryPathString; + libDirRoot = ps.legacyNativeLibraryPathString; } if (p != null && (isExternal(p) || isForwardLocked(p))) { String secureContainerId = cidFromCodePath(p.applicationInfo.getBaseCodePath()); @@ -11092,8 +11256,12 @@ public class PackageManagerService extends IPackageManager.Stub { } } // TODO: extend to measure size of split APKs - int res = mInstaller.getSizeInfo(packageName, userHandle, p.baseCodePath, libDirPath, - publicSrcDir, asecPath, getAppInstructionSetFromSettings(ps), + // TODO(multiArch): Extend getSizeInfo to look at the full subdirectory tree, + // not just the first level. + // TODO(multiArch): Extend getSizeInfo to look at *all* instruction sets, not + // just the primary. + int res = mInstaller.getSizeInfo(packageName, userHandle, p.baseCodePath, libDirRoot, + publicSrcDir, asecPath, getAppDexInstructionSets(ps), pStats); if (res < 0) { return false; @@ -12357,8 +12525,7 @@ public class PackageManagerService extends IPackageManager.Stub { } final AsecInstallArgs args = new AsecInstallArgs(cid, - getAppInstructionSetFromSettings(ps), - isForwardLocked(ps)); + getAppDexInstructionSets(ps), isForwardLocked(ps), isMultiArch(ps)); // The package status is changed only if the code path // matches between settings and the container id. if (ps.codePathString != null && ps.codePathString.equals(args.getCodePath())) { @@ -12676,16 +12843,17 @@ public class PackageManagerService extends IPackageManager.Stub { * anyway. */ if (returnCode != PackageManager.MOVE_SUCCEEDED) { - processPendingMove(new MoveParams(null, observer, 0, packageName, null, -1, user), + processPendingMove(new MoveParams(null, observer, 0, packageName, null, -1, user, false), returnCode); } else { Message msg = mHandler.obtainMessage(INIT_COPY); - final String instructionSet = getAppInstructionSet(pkg.applicationInfo); + final String[] instructionSets = getAppDexInstructionSets(pkg.applicationInfo); + final boolean multiArch = isMultiArch(pkg.applicationInfo); InstallArgs srcArgs = createInstallArgsForExisting(currFlags, pkg.applicationInfo.getCodePath(), pkg.applicationInfo.getResourcePath(), - pkg.applicationInfo.nativeLibraryDir, instructionSet); + pkg.applicationInfo.legacyNativeLibraryDir, instructionSets, multiArch); MoveParams mp = new MoveParams(srcArgs, observer, newFlags, packageName, - instructionSet, pkg.applicationInfo.uid, user); + instructionSets, pkg.applicationInfo.uid, user, multiArch); msg.obj = mp; mHandler.sendMessage(msg); } @@ -12747,12 +12915,15 @@ public class PackageManagerService extends IPackageManager.Stub { final String oldCodePath = pkg.codePath; final String newCodePath = mp.targetArgs.getCodePath(); final String newResPath = mp.targetArgs.getResourcePath(); - final String newNativePath = mp.targetArgs - .getNativeLibraryPath(); - - final File newNativeDir = new File(newNativePath); + // TODO: This assumes the new style of installation. + // should we look at legacyNativeLibraryPath ? + final String newNativeRoot = new File(pkg.codePath, LIB_DIR_NAME).getAbsolutePath(); + final File newNativeDir = new File(newNativeRoot); if (!isForwardLocked(pkg) && !isExternal(pkg)) { + // TODO(multiArch): Fix this so that it looks at the existing + // recorded CPU abis from the package. There's no need for a separate + // round of ABI scanning here. NativeLibraryHelper.Handle handle = null; try { handle = NativeLibraryHelper.Handle.create( @@ -12771,11 +12942,17 @@ public class PackageManagerService extends IPackageManager.Stub { IoUtils.closeQuietly(handle); } } + final int[] users = sUserManager.getUserIds(); if (returnCode == PackageManager.MOVE_SUCCEEDED) { for (int user : users) { + // TODO(multiArch): Fix this so that it links to the + // correct directory. We're currently pointing to root. but we + // must point to the arch specific subdirectory (if applicable). + // + // TODO(multiArch): Bogus reference to nativeLibraryDir. if (mInstaller.linkNativeLibraryDirectory(pkg.packageName, - newNativePath, user) < 0) { + newNativeRoot, user) < 0) { returnCode = PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE; } } @@ -12801,15 +12978,21 @@ public class PackageManagerService extends IPackageManager.Stub { pkg.applicationInfo.setResourcePath(newResPath); pkg.applicationInfo.setBaseResourcePath(newResPath); pkg.applicationInfo.setSplitResourcePaths(null); - pkg.applicationInfo.nativeLibraryDir = newNativePath; + // Null out the legacy nativeLibraryDir so that we stop using it and + // always derive the codepath. + pkg.applicationInfo.legacyNativeLibraryDir = null; PackageSetting ps = (PackageSetting) pkg.mExtras; ps.codePath = new File(pkg.applicationInfo.getCodePath()); ps.codePathString = ps.codePath.getPath(); - ps.resourcePath = new File( - pkg.applicationInfo.getResourcePath()); + ps.resourcePath = new File(pkg.applicationInfo.getResourcePath()); ps.resourcePathString = ps.resourcePath.getPath(); - ps.nativeLibraryPathString = newNativePath; + + // Note that we don't have to recalculate the primary and secondary + // CPU ABIs because they must already have been calculated during the + // initial install of the app. + ps.legacyNativeLibraryPathString = null; + // Set the application info flag // correctly. if ((mp.flags & PackageManager.INSTALL_EXTERNAL) != 0) { diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 284da99c9f2d..a6571cf7b1ee 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -30,9 +30,10 @@ final class PackageSetting extends PackageSettingBase { SharedUserSetting sharedUser; PackageSetting(String name, String realName, File codePath, File resourcePath, - String nativeLibraryPathString, String cpuAbiString, int pVersionCode, int pkgFlags) { - super(name, realName, codePath, resourcePath, nativeLibraryPathString, cpuAbiString, pVersionCode, - pkgFlags); + String legacyNativeLibraryPathString, String primaryCpuAbiString, + String secondaryCpuAbiString, int pVersionCode, int pkgFlags) { + super(name, realName, codePath, resourcePath, legacyNativeLibraryPathString, + primaryCpuAbiString, secondaryCpuAbiString, pVersionCode, pkgFlags); } /** diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java index f263e070ddb0..3390efe18610 100644 --- a/services/core/java/com/android/server/pm/PackageSettingBase.java +++ b/services/core/java/com/android/server/pm/PackageSettingBase.java @@ -55,8 +55,16 @@ class PackageSettingBase extends GrantedPermissions { String codePathString; File resourcePath; String resourcePathString; - String nativeLibraryPathString; - String cpuAbiString; + + /** + * The path under which native libraries for legacy apps are unpacked. + * Will be set to {@code null} for newer installs, where the path can be + * derived from {@link #codePath} unambiguously. + */ + String legacyNativeLibraryPathString; + + String primaryCpuAbiString; + String secondaryCpuAbiString; long timeStamp; long firstInstallTime; long lastUpdateTime; @@ -84,11 +92,13 @@ class PackageSettingBase extends GrantedPermissions { /* package name of the app that installed this package */ String installerPackageName; PackageSettingBase(String name, String realName, File codePath, File resourcePath, - String nativeLibraryPathString, String cpuAbiString, int pVersionCode, int pkgFlags) { + String legacyNativeLibraryPathString, String primaryCpuAbiString, + String secondaryCpuAbiString, int pVersionCode, int pkgFlags) { super(pkgFlags); this.name = name; this.realName = realName; - init(codePath, resourcePath, nativeLibraryPathString, cpuAbiString, pVersionCode); + init(codePath, resourcePath, legacyNativeLibraryPathString, primaryCpuAbiString, + secondaryCpuAbiString, pVersionCode); } /** @@ -104,8 +114,9 @@ class PackageSettingBase extends GrantedPermissions { codePathString = base.codePathString; resourcePath = base.resourcePath; resourcePathString = base.resourcePathString; - nativeLibraryPathString = base.nativeLibraryPathString; - cpuAbiString = base.cpuAbiString; + legacyNativeLibraryPathString = base.legacyNativeLibraryPathString; + primaryCpuAbiString = base.primaryCpuAbiString; + secondaryCpuAbiString = base.secondaryCpuAbiString; timeStamp = base.timeStamp; firstInstallTime = base.firstInstallTime; lastUpdateTime = base.lastUpdateTime; @@ -132,14 +143,15 @@ class PackageSettingBase extends GrantedPermissions { } - void init(File codePath, File resourcePath, String nativeLibraryPathString, - String requiredCpuAbiString, int pVersionCode) { + void init(File codePath, File resourcePath, String legacyNativeLibraryPathString, + String primaryCpuAbiString, String secondaryCpuAbiString, int pVersionCode) { this.codePath = codePath; this.codePathString = codePath.toString(); this.resourcePath = resourcePath; this.resourcePathString = resourcePath.toString(); - this.nativeLibraryPathString = nativeLibraryPathString; - this.cpuAbiString = requiredCpuAbiString; + this.legacyNativeLibraryPathString = legacyNativeLibraryPathString; + this.primaryCpuAbiString = primaryCpuAbiString; + this.secondaryCpuAbiString = secondaryCpuAbiString; this.versionCode = pVersionCode; } @@ -170,7 +182,8 @@ class PackageSettingBase extends GrantedPermissions { grantedPermissions = base.grantedPermissions; gids = base.gids; - cpuAbiString = base.cpuAbiString; + primaryCpuAbiString = base.primaryCpuAbiString; + secondaryCpuAbiString = base.secondaryCpuAbiString; timeStamp = base.timeStamp; firstInstallTime = base.firstInstallTime; lastUpdateTime = base.lastUpdateTime; diff --git a/services/core/java/com/android/server/pm/PendingPackage.java b/services/core/java/com/android/server/pm/PendingPackage.java index 36c3a349d824..85be6513cb2e 100644 --- a/services/core/java/com/android/server/pm/PendingPackage.java +++ b/services/core/java/com/android/server/pm/PendingPackage.java @@ -22,9 +22,10 @@ final class PendingPackage extends PackageSettingBase { final int sharedId; PendingPackage(String name, String realName, File codePath, File resourcePath, - String nativeLibraryPathString, String requiredCpuAbiString, int sharedId, int pVersionCode, int pkgFlags) { - super(name, realName, codePath, resourcePath, nativeLibraryPathString, requiredCpuAbiString, pVersionCode, - pkgFlags); + String nativeLibrary32PathString, String nativeLibrary64PathString, + String requiredCpuAbiString, int sharedId, int pVersionCode, int pkgFlags) { + super(name, realName, codePath, resourcePath, nativeLibrary32PathString, nativeLibrary64PathString, + requiredCpuAbiString, pVersionCode, pkgFlags); this.sharedId = sharedId; } } diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 81ea72ca9b82..71b89746551a 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -29,7 +29,13 @@ import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.ResolveInfo; import android.net.Uri; +import android.os.Binder; +import android.os.Environment; +import android.os.FileUtils; import android.os.PatternMatcher; +import android.os.Process; +import android.os.UserHandle; +import android.os.UserManager; import android.util.LogPrinter; import com.android.internal.util.FastXmlSerializer; @@ -56,12 +62,6 @@ import android.content.pm.Signature; import android.content.pm.UserInfo; import android.content.pm.PackageUserState; import android.content.pm.VerifierDeviceIdentity; -import android.os.Binder; -import android.os.Environment; -import android.os.FileUtils; -import android.os.Process; -import android.os.UserHandle; -import android.os.UserManager; import android.util.Log; import android.util.Slog; import android.util.SparseArray; @@ -317,11 +317,12 @@ final class Settings { PackageSetting getPackageLPw(PackageParser.Package pkg, PackageSetting origPackage, String realName, SharedUserSetting sharedUser, File codePath, File resourcePath, - String nativeLibraryPathString, String cpuAbiString, int pkgFlags, UserHandle user, boolean add) { + String nativeLibraryRoot, String primaryCpuAbi, String secondaryCpuAbi, int pkgFlags, + UserHandle user, boolean add) { final String name = pkg.packageName; PackageSetting p = getPackageLPw(name, origPackage, realName, sharedUser, codePath, - resourcePath, nativeLibraryPathString, cpuAbiString, pkg.mVersionCode, pkgFlags, - user, add, true /* allowInstall */); + resourcePath, nativeLibraryRoot, primaryCpuAbi, secondaryCpuAbi, + pkg.mVersionCode, pkgFlags, user, add, true /* allowInstall */); return p; } @@ -407,7 +408,8 @@ final class Settings { p.pkg.applicationInfo.flags &= ~ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; } PackageSetting ret = addPackageLPw(name, p.realName, p.codePath, p.resourcePath, - p.nativeLibraryPathString, p.cpuAbiString, p.appId, p.versionCode, p.pkgFlags); + p.legacyNativeLibraryPathString, p.primaryCpuAbiString, + p.secondaryCpuAbiString, p.appId, p.versionCode, p.pkgFlags); mDisabledSysPackages.remove(name); return ret; } @@ -421,7 +423,8 @@ final class Settings { } PackageSetting addPackageLPw(String name, String realName, File codePath, File resourcePath, - String nativeLibraryPathString, String cpuAbiString, int uid, int vc, int pkgFlags) { + String legacyNativeLibraryPathString, String primaryCpuAbiString, String secondaryCpuAbiString, + int uid, int vc, int pkgFlags) { PackageSetting p = mPackages.get(name); if (p != null) { if (p.appId == uid) { @@ -431,8 +434,8 @@ final class Settings { "Adding duplicate package, keeping first: " + name); return null; } - p = new PackageSetting(name, realName, codePath, resourcePath, nativeLibraryPathString, cpuAbiString, - vc, pkgFlags); + p = new PackageSetting(name, realName, codePath, resourcePath, + legacyNativeLibraryPathString, primaryCpuAbiString, secondaryCpuAbiString, vc, pkgFlags); p.appId = uid; if (addUserIdLPw(uid, p, name)) { mPackages.put(name, p); @@ -500,11 +503,12 @@ final class Settings { private PackageSetting getPackageLPw(String name, PackageSetting origPackage, String realName, SharedUserSetting sharedUser, File codePath, File resourcePath, - String nativeLibraryPathString, String cpuAbiString, int vc, int pkgFlags, - UserHandle installUser, boolean add, boolean allowInstall) { + String legacyNativeLibraryPathString, String primaryCpuAbiString, String secondaryCpuAbiString, + int vc, int pkgFlags, UserHandle installUser, boolean add, boolean allowInstall) { PackageSetting p = mPackages.get(name); if (p != null) { - p.cpuAbiString = cpuAbiString; + p.primaryCpuAbiString = primaryCpuAbiString; + p.secondaryCpuAbiString = secondaryCpuAbiString; if (!p.codePath.equals(codePath)) { // Check to see if its a disabled system app if ((p.pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0) { @@ -524,7 +528,7 @@ final class Settings { * package settings since we might have moved from * internal to external storage or vice versa. */ - p.nativeLibraryPathString = nativeLibraryPathString; + p.legacyNativeLibraryPathString = legacyNativeLibraryPathString; } } if (p.sharedUser != sharedUser) { @@ -548,7 +552,7 @@ final class Settings { if (origPackage != null) { // We are consuming the data from an existing package. p = new PackageSetting(origPackage.name, name, codePath, resourcePath, - nativeLibraryPathString, cpuAbiString, vc, pkgFlags); + legacyNativeLibraryPathString, primaryCpuAbiString, secondaryCpuAbiString, vc, pkgFlags); if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG, "Package " + name + " is adopting original package " + origPackage.name); // Note that we will retain the new package's signature so @@ -565,7 +569,7 @@ final class Settings { p.setTimeStamp(codePath.lastModified()); } else { p = new PackageSetting(name, realName, codePath, resourcePath, - nativeLibraryPathString, cpuAbiString, vc, pkgFlags); + legacyNativeLibraryPathString, primaryCpuAbiString, secondaryCpuAbiString, vc, pkgFlags); p.setTimeStamp(codePath.lastModified()); p.sharedUser = sharedUser; // If this is not a system app, it starts out stopped. @@ -699,14 +703,15 @@ final class Settings { p.resourcePath = new File(resourcePath); p.resourcePathString = resourcePath; } - // Update the native library path if needed - final String nativeLibraryPath = pkg.applicationInfo.nativeLibraryDir; - if (nativeLibraryPath != null - && !nativeLibraryPath.equalsIgnoreCase(p.nativeLibraryPathString)) { - p.nativeLibraryPathString = nativeLibraryPath; + // Update the native library paths if needed + final String nativeLibraryRoot = pkg.applicationInfo.legacyNativeLibraryDir; + if (nativeLibraryRoot != null && !nativeLibraryRoot.equalsIgnoreCase(p.legacyNativeLibraryPathString)) { + p.legacyNativeLibraryPathString = nativeLibraryRoot; } + // Update the required Cpu Abi - p.cpuAbiString = pkg.applicationInfo.cpuAbi; + p.primaryCpuAbiString = pkg.applicationInfo.primaryCpuAbi; + p.secondaryCpuAbiString = pkg.applicationInfo.secondaryCpuAbi; // Update version code if needed if (pkg.mVersionCode != p.versionCode) { p.versionCode = pkg.mVersionCode; @@ -1861,12 +1866,16 @@ final class Settings { if (!pkg.resourcePathString.equals(pkg.codePathString)) { serializer.attribute(null, "resourcePath", pkg.resourcePathString); } - if (pkg.nativeLibraryPathString != null) { - serializer.attribute(null, "nativeLibraryPath", pkg.nativeLibraryPathString); + if (pkg.legacyNativeLibraryPathString != null) { + serializer.attribute(null, "nativeLibraryPath", pkg.legacyNativeLibraryPathString); } - if (pkg.cpuAbiString != null) { - serializer.attribute(null, "requiredCpuAbi", pkg.cpuAbiString); + if (pkg.primaryCpuAbiString != null) { + serializer.attribute(null, "primaryCpuAbi", pkg.primaryCpuAbiString); } + if (pkg.secondaryCpuAbiString != null) { + serializer.attribute(null, "secondaryCpuAbi", pkg.secondaryCpuAbiString); + } + if (pkg.sharedUser == null) { serializer.attribute(null, "userId", Integer.toString(pkg.appId)); } else { @@ -1906,12 +1915,17 @@ final class Settings { if (!pkg.resourcePathString.equals(pkg.codePathString)) { serializer.attribute(null, "resourcePath", pkg.resourcePathString); } - if (pkg.nativeLibraryPathString != null) { - serializer.attribute(null, "nativeLibraryPath", pkg.nativeLibraryPathString); + + if (pkg.legacyNativeLibraryPathString != null) { + serializer.attribute(null, "nativeLibraryPath", pkg.legacyNativeLibraryPathString); + } + if (pkg.primaryCpuAbiString != null) { + serializer.attribute(null, "primaryCpuAbi", pkg.primaryCpuAbiString); } - if (pkg.cpuAbiString != null) { - serializer.attribute(null, "requiredCpuAbi", pkg.cpuAbiString); + if (pkg.secondaryCpuAbiString != null) { + serializer.attribute(null, "secondaryCpuAbi", pkg.secondaryCpuAbiString); } + serializer.attribute(null, "flags", Integer.toString(pkg.pkgFlags)); serializer.attribute(null, "ft", Long.toHexString(pkg.timeStamp)); serializer.attribute(null, "it", Long.toHexString(pkg.firstInstallTime)); @@ -2219,7 +2233,8 @@ final class Settings { if (idObj != null && idObj instanceof SharedUserSetting) { PackageSetting p = getPackageLPw(pp.name, null, pp.realName, (SharedUserSetting) idObj, pp.codePath, pp.resourcePath, - pp.nativeLibraryPathString, pp.cpuAbiString, pp.versionCode, pp.pkgFlags, + pp.legacyNativeLibraryPathString, pp.primaryCpuAbiString, + pp.secondaryCpuAbiString, pp.versionCode, pp.pkgFlags, null, true /* add */, false /* allowInstall */); if (p == null) { PackageManagerService.reportSettingsProblem(Log.WARN, @@ -2638,8 +2653,16 @@ final class Settings { String realName = parser.getAttributeValue(null, "realName"); String codePathStr = parser.getAttributeValue(null, "codePath"); String resourcePathStr = parser.getAttributeValue(null, "resourcePath"); - String nativeLibraryPathStr = parser.getAttributeValue(null, "nativeLibraryPath"); - String cpuAbiString = parser.getAttributeValue(null, "requiredCpuAbi"); + + String legacyCpuAbiStr = parser.getAttributeValue(null, "requiredCpuAbi"); + String legacyNativeLibraryPathStr = parser.getAttributeValue(null, "nativeLibraryPath"); + + String primaryCpuAbiStr = parser.getAttributeValue(null, "primaryCpuAbi"); + String secondaryCpuAbiStr = parser.getAttributeValue(null, "secondaryCpuAbi"); + + if (primaryCpuAbiStr == null && legacyCpuAbiStr != null) { + primaryCpuAbiStr = legacyCpuAbiStr; + } if (resourcePathStr == null) { resourcePathStr = codePathStr; @@ -2660,7 +2683,8 @@ final class Settings { pkgFlags |= ApplicationInfo.FLAG_PRIVILEGED; } PackageSetting ps = new PackageSetting(name, realName, codePathFile, - new File(resourcePathStr), nativeLibraryPathStr, cpuAbiString, versionCode, pkgFlags); + new File(resourcePathStr), legacyNativeLibraryPathStr, primaryCpuAbiStr, + secondaryCpuAbiStr, versionCode, pkgFlags); String timeStampStr = parser.getAttributeValue(null, "ft"); if (timeStampStr != null) { try { @@ -2726,8 +2750,10 @@ final class Settings { String sharedIdStr = null; String codePathStr = null; String resourcePathStr = null; - String nativeLibraryPathStr = null; - String cpuAbiString = null; + String legacyCpuAbiString = null; + String legacyNativeLibraryPathStr = null; + String primaryCpuAbiString = null; + String secondaryCpuAbiString = null; String systemStr = null; String installerPackageName = null; String uidError = null; @@ -2746,9 +2772,17 @@ final class Settings { sharedIdStr = parser.getAttributeValue(null, "sharedUserId"); codePathStr = parser.getAttributeValue(null, "codePath"); resourcePathStr = parser.getAttributeValue(null, "resourcePath"); - nativeLibraryPathStr = parser.getAttributeValue(null, "nativeLibraryPath"); - cpuAbiString = parser.getAttributeValue(null, "requiredCpuAbi"); + legacyCpuAbiString = parser.getAttributeValue(null, "requiredCpuAbi"); + + legacyNativeLibraryPathStr = parser.getAttributeValue(null, "nativeLibraryPath"); + primaryCpuAbiString = parser.getAttributeValue(null, "primaryCpuAbi"); + secondaryCpuAbiString = parser.getAttributeValue(null, "secondaryCpuAbi"); + + if (primaryCpuAbiString == null && legacyCpuAbiString != null) { + primaryCpuAbiString = legacyCpuAbiString; + } +; version = parser.getAttributeValue(null, "version"); if (version != null) { try { @@ -2825,8 +2859,8 @@ final class Settings { + parser.getPositionDescription()); } else if (userId > 0) { packageSetting = addPackageLPw(name.intern(), realName, new File(codePathStr), - new File(resourcePathStr), nativeLibraryPathStr, cpuAbiString, userId, versionCode, - pkgFlags); + new File(resourcePathStr), legacyNativeLibraryPathStr, primaryCpuAbiString, + secondaryCpuAbiString, userId, versionCode, pkgFlags); if (PackageManagerService.DEBUG_SETTINGS) Log.i(PackageManagerService.TAG, "Reading package " + name + ": userId=" + userId + " pkg=" + packageSetting); @@ -2843,8 +2877,8 @@ final class Settings { userId = sharedIdStr != null ? Integer.parseInt(sharedIdStr) : 0; if (userId > 0) { packageSetting = new PendingPackage(name.intern(), realName, new File( - codePathStr), new File(resourcePathStr), nativeLibraryPathStr, cpuAbiString, userId, - versionCode, pkgFlags); + codePathStr), new File(resourcePathStr), legacyNativeLibraryPathStr, + primaryCpuAbiString, legacyCpuAbiString, userId, versionCode, pkgFlags); packageSetting.setTimeStamp(timeStamp); packageSetting.firstInstallTime = firstInstallTime; packageSetting.lastUpdateTime = lastUpdateTime; @@ -2871,8 +2905,9 @@ final class Settings { if (packageSetting != null) { packageSetting.uidError = "true".equals(uidError); packageSetting.installerPackageName = installerPackageName; - packageSetting.nativeLibraryPathString = nativeLibraryPathStr; - packageSetting.cpuAbiString = cpuAbiString; + packageSetting.legacyNativeLibraryPathString = legacyNativeLibraryPathStr; + packageSetting.primaryCpuAbiString = primaryCpuAbiString; + packageSetting.secondaryCpuAbiString = secondaryCpuAbiString; // Handle legacy string here for single-user mode final String enabledStr = parser.getAttributeValue(null, ATTR_ENABLED); if (enabledStr != null) { @@ -3417,8 +3452,9 @@ final class Settings { pw.print(prefix); pw.print(" pkg="); pw.println(ps.pkg); pw.print(prefix); pw.print(" codePath="); pw.println(ps.codePathString); pw.print(prefix); pw.print(" resourcePath="); pw.println(ps.resourcePathString); - pw.print(prefix); pw.print(" nativeLibraryPath="); pw.println(ps.nativeLibraryPathString); - pw.print(prefix); pw.print(" requiredCpuAbi="); pw.println(ps.cpuAbiString); + pw.print(prefix); pw.print(" legacyNativeLibraryDir="); pw.println(ps.legacyNativeLibraryPathString); + pw.print(prefix); pw.print(" primaryCpuAbi="); pw.println(ps.primaryCpuAbiString); + pw.print(prefix); pw.print(" secondaryCpuAbi="); pw.println(ps.secondaryCpuAbiString); pw.print(prefix); pw.print(" versionCode="); pw.print(ps.versionCode); if (ps.pkg != null) { pw.print(" targetSdk="); pw.print(ps.pkg.applicationInfo.targetSdkVersion); diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index bda10de01df0..8387b6522cee 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -130,7 +130,8 @@ class WindowStateAnimator { // For debugging, this is the last information given to the surface flinger. boolean mSurfaceShown; - float mSurfaceX, mSurfaceY, mSurfaceW, mSurfaceH; + float mSurfaceX, mSurfaceY; + float mSurfaceW, mSurfaceH; int mSurfaceLayer; float mSurfaceAlpha; @@ -666,117 +667,149 @@ class WindowStateAnimator { } SurfaceControl createSurfaceLocked() { + final WindowState w = mWin; if (mSurfaceControl == null) { if (DEBUG_ANIM || DEBUG_ORIENTATION) Slog.i(TAG, "createSurface " + this + ": mDrawState=DRAW_PENDING"); mDrawState = DRAW_PENDING; - if (mWin.mAppToken != null) { - if (mWin.mAppToken.mAppAnimator.animation == null) { - mWin.mAppToken.allDrawn = false; - mWin.mAppToken.deferClearAllDrawn = false; + if (w.mAppToken != null) { + if (w.mAppToken.mAppAnimator.animation == null) { + w.mAppToken.allDrawn = false; + w.mAppToken.deferClearAllDrawn = false; } else { // Currently animating, persist current state of allDrawn until animation // is complete. - mWin.mAppToken.deferClearAllDrawn = true; + w.mAppToken.deferClearAllDrawn = true; } } - mService.makeWindowFreezingScreenIfNeededLocked(mWin); + mService.makeWindowFreezingScreenIfNeededLocked(w); int flags = SurfaceControl.HIDDEN; - final WindowManager.LayoutParams attrs = mWin.mAttrs; + final WindowManager.LayoutParams attrs = w.mAttrs; if ((attrs.flags&WindowManager.LayoutParams.FLAG_SECURE) != 0) { flags |= SurfaceControl.SECURE; } - if (DEBUG_VISIBILITY) Slog.v( - TAG, "Creating surface in session " - + mSession.mSurfaceSession + " window " + this - + " w=" + mWin.mCompatFrame.width() - + " h=" + mWin.mCompatFrame.height() + " format=" - + attrs.format + " flags=" + flags); - int w = mWin.mCompatFrame.width(); - int h = mWin.mCompatFrame.height(); + int width; + int height; if ((attrs.flags & LayoutParams.FLAG_SCALED) != 0) { // for a scaled surface, we always want the requested // size. - w = mWin.mRequestedWidth; - h = mWin.mRequestedHeight; + width = w.mRequestedWidth; + height = w.mRequestedHeight; + } else { + width = w.mCompatFrame.width(); + height = w.mCompatFrame.height(); } // Something is wrong and SurfaceFlinger will not like this, // try to revert to sane values - if (w <= 0) w = 1; - if (h <= 0) h = 1; + if (width <= 0) { + width = 1; + } + if (height <= 0) { + height = 1; + } + + float left = w.mFrame.left + w.mXOffset; + float top = w.mFrame.top + w.mYOffset; + + // Adjust for surface insets. + width += attrs.shadowInsets.left + attrs.shadowInsets.right; + height += attrs.shadowInsets.top + attrs.shadowInsets.bottom; + left -= attrs.shadowInsets.left; + top -= attrs.shadowInsets.top; + if (DEBUG_VISIBILITY) { + Slog.v(TAG, "Creating surface in session " + + mSession.mSurfaceSession + " window " + this + + " w=" + width + " h=" + height + + " x=" + left + " y=" + top + + " format=" + attrs.format + " flags=" + flags); + } + + // We may abort, so initialize to defaults. mSurfaceShown = false; mSurfaceLayer = 0; mSurfaceAlpha = 0; mSurfaceX = 0; mSurfaceY = 0; - mSurfaceW = w; - mSurfaceH = h; - mWin.mLastSystemDecorRect.set(0, 0, 0, 0); + w.mLastSystemDecorRect.set(0, 0, 0, 0); + + // Set up surface control with initial size. try { + mSurfaceW = width; + mSurfaceH = height; + final boolean isHwAccelerated = (attrs.flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0; final int format = isHwAccelerated ? PixelFormat.TRANSLUCENT : attrs.format; if (!PixelFormat.formatHasAlpha(attrs.format)) { flags |= SurfaceControl.OPAQUE; } + if (DEBUG_SURFACE_TRACE) { mSurfaceControl = new SurfaceTrace( mSession.mSurfaceSession, attrs.getTitle().toString(), - w, h, format, flags); + width, height, format, flags); } else { mSurfaceControl = new SurfaceControl( mSession.mSurfaceSession, attrs.getTitle().toString(), - w, h, format, flags); + width, height, format, flags); + } + + w.mHasSurface = true; + + if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) { + Slog.i(TAG, " CREATE SURFACE " + + mSurfaceControl + " IN SESSION " + + mSession.mSurfaceSession + + ": pid=" + mSession.mPid + " format=" + + attrs.format + " flags=0x" + + Integer.toHexString(flags) + + " / " + this); } - mWin.mHasSurface = true; - if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG, - " CREATE SURFACE " - + mSurfaceControl + " IN SESSION " - + mSession.mSurfaceSession - + ": pid=" + mSession.mPid + " format=" - + attrs.format + " flags=0x" - + Integer.toHexString(flags) - + " / " + this); } catch (OutOfResourcesException e) { - mWin.mHasSurface = false; + w.mHasSurface = false; Slog.w(TAG, "OutOfResourcesException creating surface"); mService.reclaimSomeSurfaceMemoryLocked(this, "create", true); mDrawState = NO_SURFACE; return null; } catch (Exception e) { - mWin.mHasSurface = false; + w.mHasSurface = false; Slog.e(TAG, "Exception creating surface", e); mDrawState = NO_SURFACE; return null; } - if (WindowManagerService.localLOGV) Slog.v( - TAG, "Got surface: " + mSurfaceControl - + ", set left=" + mWin.mFrame.left + " top=" + mWin.mFrame.top - + ", animLayer=" + mAnimLayer); + if (WindowManagerService.localLOGV) { + Slog.v(TAG, "Got surface: " + mSurfaceControl + + ", set left=" + w.mFrame.left + " top=" + w.mFrame.top + + ", animLayer=" + mAnimLayer); + } + if (SHOW_LIGHT_TRANSACTIONS) { Slog.i(TAG, ">>> OPEN TRANSACTION createSurfaceLocked"); - WindowManagerService.logSurface(mWin, "CREATE pos=(" - + mWin.mFrame.left + "," + mWin.mFrame.top + ") (" - + mWin.mCompatFrame.width() + "x" + mWin.mCompatFrame.height() + WindowManagerService.logSurface(w, "CREATE pos=(" + + w.mFrame.left + "," + w.mFrame.top + ") (" + + w.mCompatFrame.width() + "x" + w.mCompatFrame.height() + "), layer=" + mAnimLayer + " HIDE", null); } + + // Start a new transaction and apply position & offset. SurfaceControl.openTransaction(); try { + mSurfaceX = left; + mSurfaceY = top; + try { - mSurfaceX = mWin.mFrame.left + mWin.mXOffset; - mSurfaceY = mWin.mFrame.top + mWin.mYOffset; - mSurfaceControl.setPosition(mSurfaceX, mSurfaceY); + mSurfaceControl.setPosition(left, top); mSurfaceLayer = mAnimLayer; - final DisplayContent displayContent = mWin.getDisplayContent(); + final DisplayContent displayContent = w.getDisplayContent(); if (displayContent != null) { mSurfaceControl.setLayerStack(displayContent.getDisplay().getLayerStack()); } @@ -1107,14 +1140,27 @@ class WindowStateAnimator { void applyDecorRect(final Rect decorRect) { final WindowState w = mWin; + int width = w.mFrame.width(); + int height = w.mFrame.height(); + // Compute the offset of the window in relation to the decor rect. - final int offX = w.mXOffset + w.mFrame.left; - final int offY = w.mYOffset + w.mFrame.top; + int left = w.mXOffset + w.mFrame.left; + int top = w.mYOffset + w.mFrame.top; + + // Adjust for surface insets. + final WindowManager.LayoutParams attrs = w.mAttrs; + width += attrs.shadowInsets.left + attrs.shadowInsets.right; + height += attrs.shadowInsets.top + attrs.shadowInsets.bottom; + left -= attrs.shadowInsets.left; + top -= attrs.shadowInsets.top; + // Initialize the decor rect to the entire frame. - w.mSystemDecorRect.set(0, 0, w.mFrame.width(), w.mFrame.height()); + w.mSystemDecorRect.set(0, 0, width, height); + // Intersect with the decor rect, offsetted by window position. - w.mSystemDecorRect.intersect(decorRect.left-offX, decorRect.top-offY, - decorRect.right-offX, decorRect.bottom-offY); + w.mSystemDecorRect.intersect(decorRect.left - left, decorRect.top - top, + decorRect.right - left, decorRect.bottom - top); + // If size compatibility is being applied to the window, the // surface is scaled relative to the screen. Also apply this // scaling to the crop rect. We aren't using the standard rect @@ -1212,10 +1258,12 @@ class WindowStateAnimator { void setSurfaceBoundariesLocked(final boolean recoveringMemory) { final WindowState w = mWin; - int width, height; + + int width; + int height; if ((w.mAttrs.flags & LayoutParams.FLAG_SCALED) != 0) { - // for a scaled surface, we just want to use - // the requested size. + // for a scaled surface, we always want the requested + // size. width = w.mRequestedWidth; height = w.mRequestedHeight; } else { @@ -1223,42 +1271,52 @@ class WindowStateAnimator { height = w.mCompatFrame.height(); } + // Something is wrong and SurfaceFlinger will not like this, + // try to revert to sane values if (width < 1) { width = 1; } if (height < 1) { height = 1; } - final boolean surfaceResized = mSurfaceW != width || mSurfaceH != height; - if (surfaceResized) { - mSurfaceW = width; - mSurfaceH = height; - } - final float left = w.mShownFrame.left; - final float top = w.mShownFrame.top; - if (mSurfaceX != left || mSurfaceY != top) { + float left = w.mShownFrame.left; + float top = w.mShownFrame.top; + + // Adjust for surface insets. + final LayoutParams attrs = w.getAttrs(); + width += attrs.shadowInsets.left + attrs.shadowInsets.right; + height += attrs.shadowInsets.top + attrs.shadowInsets.bottom; + left -= attrs.shadowInsets.left; + top -= attrs.shadowInsets.top; + + final boolean surfaceMoved = mSurfaceX != left || mSurfaceY != top; + if (surfaceMoved) { + mSurfaceX = left; + mSurfaceY = top; + try { if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w, "POS " + left + ", " + top, null); - mSurfaceX = left; - mSurfaceY = top; mSurfaceControl.setPosition(left, top); } catch (RuntimeException e) { Slog.w(TAG, "Error positioning surface of " + w - + " pos=(" + left - + "," + top + ")", e); + + " pos=(" + left + "," + top + ")", e); if (!recoveringMemory) { mService.reclaimSomeSurfaceMemoryLocked(this, "position", true); } } } + final boolean surfaceResized = mSurfaceW != width || mSurfaceH != height; if (surfaceResized) { + mSurfaceW = width; + mSurfaceH = height; + mSurfaceResized = true; + try { if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w, "SIZE " + width + "x" + height, null); - mSurfaceResized = true; mSurfaceControl.setSize(width, height); mAnimator.setPendingLayoutChanges(w.getDisplayId(), WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER); diff --git a/services/core/jni/com_android_server_connectivity_Vpn.cpp b/services/core/jni/com_android_server_connectivity_Vpn.cpp index bf34a748061d..6031906717c1 100644 --- a/services/core/jni/com_android_server_connectivity_Vpn.cpp +++ b/services/core/jni/com_android_server_connectivity_Vpn.cpp @@ -187,96 +187,6 @@ static int set_addresses(const char *name, const char *addresses) return count; } -static int set_routes(const char *name, const char *routes) -{ - int index = get_interface_index(name); - if (index < 0) { - return index; - } - - rtentry rt4; - memset(&rt4, 0, sizeof(rt4)); - rt4.rt_dev = (char *)name; - rt4.rt_flags = RTF_UP; - rt4.rt_dst.sa_family = AF_INET; - rt4.rt_genmask.sa_family = AF_INET; - - in6_rtmsg rt6; - memset(&rt6, 0, sizeof(rt6)); - rt6.rtmsg_ifindex = index; - rt6.rtmsg_flags = RTF_UP; - - char address[65]; - int prefix; - int chars; - int count = 0; - - while (sscanf(routes, " %64[^/]/%d %n", address, &prefix, &chars) == 2) { - routes += chars; - - if (strchr(address, ':')) { - // Add an IPv6 route. - if (inet_pton(AF_INET6, address, &rt6.rtmsg_dst) != 1 || - prefix < 0 || prefix > 128) { - count = BAD_ARGUMENT; - break; - } - - rt6.rtmsg_dst_len = prefix ? prefix : 1; - if (ioctl(inet6, SIOCADDRT, &rt6) && errno != EEXIST) { - count = (errno == EINVAL) ? BAD_ARGUMENT : SYSTEM_ERROR; - break; - } - - if (!prefix) { - // Split the route instead of replacing the default route. - rt6.rtmsg_dst.s6_addr[0] ^= 0x80; - if (ioctl(inet6, SIOCADDRT, &rt6) && errno != EEXIST) { - count = SYSTEM_ERROR; - break; - } - } - } else { - // Add an IPv4 route. - if (inet_pton(AF_INET, address, as_in_addr(&rt4.rt_dst)) != 1 || - prefix < 0 || prefix > 32) { - count = BAD_ARGUMENT; - break; - } - - in_addr_t mask = prefix ? (~0 << (32 - prefix)) : 0x80000000; - *as_in_addr(&rt4.rt_genmask) = htonl(mask); - if (ioctl(inet4, SIOCADDRT, &rt4) && errno != EEXIST) { - count = (errno == EINVAL) ? BAD_ARGUMENT : SYSTEM_ERROR; - break; - } - - if (!prefix) { - // Split the route instead of replacing the default route. - *as_in_addr(&rt4.rt_dst) ^= htonl(0x80000000); - if (ioctl(inet4, SIOCADDRT, &rt4) && errno != EEXIST) { - count = SYSTEM_ERROR; - break; - } - } - } - ALOGD("Route added on %s: %s/%d", name, address, prefix); - ++count; - } - - if (count == BAD_ARGUMENT) { - ALOGE("Invalid route: %s/%d", address, prefix); - } else if (count == SYSTEM_ERROR) { - ALOGE("Cannot add route: %s/%d: %s", - address, prefix, strerror(errno)); - } else if (*routes) { - ALOGE("Invalid route: %s", routes); - count = BAD_ARGUMENT; - } - - return count; -} - static int reset_interface(const char *name) { ifreq ifr4; @@ -366,39 +276,6 @@ error: return count; } -static jint setRoutes(JNIEnv *env, jobject thiz, jstring jName, - jstring jRoutes) -{ - const char *name = NULL; - const char *routes = NULL; - int count = -1; - - name = jName ? env->GetStringUTFChars(jName, NULL) : NULL; - if (!name) { - jniThrowNullPointerException(env, "name"); - goto error; - } - routes = jRoutes ? env->GetStringUTFChars(jRoutes, NULL) : NULL; - if (!routes) { - jniThrowNullPointerException(env, "routes"); - goto error; - } - count = set_routes(name, routes); - if (count < 0) { - throwException(env, count, "Cannot set route"); - count = -1; - } - -error: - if (name) { - env->ReleaseStringUTFChars(jName, name); - } - if (routes) { - env->ReleaseStringUTFChars(jRoutes, routes); - } - return count; -} - static void reset(JNIEnv *env, jobject thiz, jstring jName) { const char *name = jName ? env->GetStringUTFChars(jName, NULL) : NULL; @@ -430,7 +307,6 @@ static JNINativeMethod gMethods[] = { {"jniCreate", "(I)I", (void *)create}, {"jniGetName", "(I)Ljava/lang/String;", (void *)getName}, {"jniSetAddresses", "(Ljava/lang/String;Ljava/lang/String;)I", (void *)setAddresses}, - {"jniSetRoutes", "(Ljava/lang/String;Ljava/lang/String;)I", (void *)setRoutes}, {"jniReset", "(Ljava/lang/String;)V", (void *)reset}, {"jniCheck", "(Ljava/lang/String;)I", (void *)check}, }; diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java index a1af8cb79f43..8aae69554ad7 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java @@ -47,6 +47,7 @@ import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.expectLastCall; import static org.easymock.EasyMock.isA; +import android.app.AlarmClockInfo; import android.app.AlarmManager; import android.app.IAlarmManager; import android.app.PendingIntent; @@ -879,7 +880,7 @@ public class NetworkStatsServiceTest extends AndroidTestCase { expectLastCall().anyTimes(); mAlarmManager.set(eq(AlarmManager.ELAPSED_REALTIME), anyLong(), anyLong(), anyLong(), - isA(PendingIntent.class), isA(WorkSource.class)); + isA(PendingIntent.class), isA(WorkSource.class), isA(AlarmClockInfo.class)); expectLastCall().atLeastOnce(); mNetManager.setGlobalAlert(anyLong()); diff --git a/telecomm/java/android/telecomm/CallCapabilities.java b/telecomm/java/android/telecomm/CallCapabilities.java index 2e0152af6f92..924526eae9b3 100644 --- a/telecomm/java/android/telecomm/CallCapabilities.java +++ b/telecomm/java/android/telecomm/CallCapabilities.java @@ -42,6 +42,12 @@ public final class CallCapabilities { /** Call supports generic conference mode. */ public static final int GENERIC_CONFERENCE = 0x00000080; + /** Local device supports video telephony. */ + public static final int SUPPORTS_VT_LOCAL = 0x00000100; + + /** Remote device supports video telephony. */ + public static final int SUPPORTS_VT_REMOTE = 0x00000200; + public static final int ALL = HOLD | SUPPORT_HOLD | MERGE_CALLS | SWAP_CALLS | ADD_CALL | RESPOND_VIA_TEXT | MUTE | GENERIC_CONFERENCE; @@ -72,6 +78,12 @@ public final class CallCapabilities { if ((capabilities & GENERIC_CONFERENCE) != 0) { builder.append(" GENERIC_CONFERENCE"); } + if ((capabilities & SUPPORTS_VT_LOCAL) != 0) { + builder.append(" SUPPORTS_VT_LOCAL"); + } + if ((capabilities & SUPPORTS_VT_REMOTE) != 0) { + builder.append(" SUPPORTS_VT_REMOTE"); + } builder.append("]"); return builder.toString(); } diff --git a/telecomm/java/android/telecomm/Connection.java b/telecomm/java/android/telecomm/Connection.java index 2fd001abfc74..05b0062e8fbd 100644 --- a/telecomm/java/android/telecomm/Connection.java +++ b/telecomm/java/android/telecomm/Connection.java @@ -112,6 +112,7 @@ public abstract class Connection { private boolean mRequestingRingback = false; private int mCallCapabilities; private Connection mParentConnection; + private CallVideoProvider mCallVideoProvider; private boolean mAudioModeIsVoip; private StatusHints mStatusHints; @@ -349,11 +350,16 @@ public abstract class Connection { * @param callVideoProvider The call video provider. */ public final void setCallVideoProvider(CallVideoProvider callVideoProvider) { + mCallVideoProvider = callVideoProvider; for (Listener l : mListeners) { l.onSetCallVideoProvider(this, callVideoProvider); } } + public final CallVideoProvider getCallVideoProvider() { + return mCallVideoProvider; + } + /** * Sets state to disconnected. * diff --git a/telecomm/java/android/telecomm/ConnectionService.java b/telecomm/java/android/telecomm/ConnectionService.java index 1bc184bbb752..d0ad0cc770e3 100644 --- a/telecomm/java/android/telecomm/ConnectionService.java +++ b/telecomm/java/android/telecomm/ConnectionService.java @@ -25,6 +25,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.telecomm.CallVideoProvider; import com.android.internal.os.SomeArgs; import com.android.internal.telecomm.IConnectionService; @@ -688,6 +689,12 @@ public abstract class ConnectionService extends Service { mIdByConnection.put(connection, callId); connection.addConnectionListener(mConnectionListener); onConnectionAdded(connection); + + // Trigger listeners for properties set before connection listener was added. + CallVideoProvider callVideoProvider = connection.getCallVideoProvider(); + if (callVideoProvider != null) { + connection.setCallVideoProvider(callVideoProvider); + } } private void removeConnection(Connection connection) { diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable25.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable25.xml new file mode 100644 index 000000000000..a3f044787598 --- /dev/null +++ b/tests/VectorDrawableTest/res/drawable/vector_drawable25.xml @@ -0,0 +1,93 @@ +<!-- + 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" > + + <size + android:height="64dp" + android:width="64dp" /> + + <viewport + android:viewportHeight="400" + android:viewportWidth="400" /> + + <group + android:name="FirstLevelGroup" + android:alpha="0.9" + android:translateX="100.0" + android:translateY="0.0" > + <group + android:name="SecondLevelGroup1" + android:alpha="0.9" + android:translateX="-100.0" + android:translateY="50.0" > + <path + android:fill="#FF00FF00" + android:pathData="@string/rectangle200" /> + + <group + android:name="ThridLevelGroup1" + android:alpha="0.9" + android:translateX="-100.0" + android:translateY="50.0" > + <path + android:fill="#FF0000FF" + android:pathData="@string/rectangle200" /> + </group> + <group + android:name="ThridLevelGroup2" + android:alpha="0.8" + android:translateX="100.0" + android:translateY="50.0" > + <path + android:fill="#FF000000" + android:pathData="@string/rectangle200" /> + </group> + </group> + <group + android:name="SecondLevelGroup2" + android:alpha="0.8" + android:translateX="100.0" + android:translateY="50.0" > + <path + android:fill="#FF0000FF" + android:pathData="@string/rectangle200" /> + + <group + android:name="ThridLevelGroup3" + android:alpha="0.9" + android:translateX="-100.0" + android:translateY="50.0" > + <path + android:fill="#FFFF0000" + android:pathData="@string/rectangle200" /> + </group> + <group + android:name="ThridLevelGroup4" + android:alpha="0.8" + android:translateX="100.0" + android:translateY="50.0" > + <path + android:fill="#FF00FF00" + android:pathData="@string/rectangle200" /> + </group> + </group> + + <path + android:fill="#FFFF0000" + android:pathData="@string/rectangle200" /> + </group> + +</vector>
\ No newline at end of file diff --git a/tests/VectorDrawableTest/res/values/strings.xml b/tests/VectorDrawableTest/res/values/strings.xml index 54cffa87d83c..a550549faa37 100644 --- a/tests/VectorDrawableTest/res/values/strings.xml +++ b/tests/VectorDrawableTest/res/values/strings.xml @@ -24,5 +24,5 @@ <string name="equal2"> "M300,35 l 0,-35 70,0 0,35z M300,105 l 70,0 0,35 -70,0z"</string> <string name="round_box">"m2.10001,-6c-1.9551,0 -0.5,0.02499 -2.10001,0.02499c-1.575,0 0.0031,-0.02499 -1.95,-0.02499c-2.543,0 -4,2.2816 -4,4.85001c0,3.52929 0.25,6.25 5.95,6.25c5.7,0 6,-2.72071 6,-6.25c0,-2.56841 -1.35699,-4.85001 -3.89999,-4.85001"</string> <string name="heart"> "m4.5,-7c-1.95509,0 -3.83009,1.26759 -4.5,3c-0.66991,-1.73241 -2.54691,-3 -4.5,-3c-2.543,0 -4.5,1.93159 -4.5,4.5c0,3.5293 3.793,6.2578 9,11.5c5.207,-5.2422 9,-7.9707 9,-11.5c0,-2.56841 -1.957,-4.5 -4.5,-4.5"</string> - + <string name="rectangle200">"M 0,0 l 200,0 l 0, 200 l -200, 0 z"</string> </resources>
\ No newline at end of file diff --git a/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java b/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java index 814deb8894d5..e8b69523ee18 100644 --- a/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java +++ b/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java @@ -52,6 +52,7 @@ public class VectorDrawablePerformance extends Activity { R.drawable.vector_drawable22, R.drawable.vector_drawable23, R.drawable.vector_drawable24, + R.drawable.vector_drawable25, }; @Override diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index e83eed7283fc..22ba9244d512 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -18,6 +18,7 @@ package android.net.wifi; import android.net.wifi.BatchedScanResult; import android.net.wifi.BatchedScanSettings; +import android.net.wifi.WifiAdapter; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; import android.net.wifi.ScanSettings; @@ -25,6 +26,7 @@ import android.net.wifi.WifiChannel; import android.net.wifi.ScanResult; import android.net.DhcpInfo; + import android.os.Messenger; import android.os.WorkSource; @@ -35,6 +37,8 @@ import android.os.WorkSource; */ interface IWifiManager { + List<WifiAdapter> getAdaptors(); + List<WifiConfiguration> getConfiguredNetworks(); int addOrUpdateNetwork(in WifiConfiguration config); diff --git a/wifi/java/android/net/wifi/WifiAdapter.aidl b/wifi/java/android/net/wifi/WifiAdapter.aidl new file mode 100644 index 000000000000..931da92cf1b6 --- /dev/null +++ b/wifi/java/android/net/wifi/WifiAdapter.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2008, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi; + +parcelable WifiAdapter; diff --git a/wifi/java/android/net/wifi/WifiAdapter.java b/wifi/java/android/net/wifi/WifiAdapter.java new file mode 100644 index 000000000000..f6ee730a0ef5 --- /dev/null +++ b/wifi/java/android/net/wifi/WifiAdapter.java @@ -0,0 +1,135 @@ +/* + * 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.net.wifi; + +import android.os.Parcel; +import android.os.Parcelable; + +/** @hide */ +public class WifiAdapter implements Parcelable { + + /* Keep this list in sync with wifi_hal.h */ + public static final int WIFI_FEATURE_INFRA = 0x0001; // Basic infrastructure mode + public static final int WIFI_FEATURE_INFRA_5G = 0x0002; // Support for 5 GHz Band + public static final int WIFI_FEATURE_PASSPOINT = 0x0004; // Support for GAS/ANQP + public static final int WIFI_FEATURE_P2P = 0x0008; // Wifi-Direct + public static final int WIFI_FEATURE_MOBILE_HOTSPOT = 0x0010; // Soft AP + public static final int WIFI_FEATURE_SCANNER = 0x0020; // WifiScanner APIs + public static final int WIFI_FEATURE_NAN = 0x0040; // Neighbor Awareness Networking + public static final int WIFI_FEATURE_D2D_RTT = 0x0080; // Device-to-device RTT + public static final int WIFI_FEATURE_D2AP_RTT = 0x0100; // Device-to-AP RTT + public static final int WIFI_FEATURE_BATCH_SCAN = 0x0200; // Batched Scan (deprecated) + public static final int WIFI_FEATURE_PNO = 0x0400; // Preferred network offload + public static final int WIFI_FEATURE_ADDITIONAL_STA = 0x0800; // Support for two STAs + public static final int WIFI_FEATURE_TDLS = 0x1000; // Tunnel directed link setup + public static final int WIFI_FEATURE_TDLS_OFFCHANNEL = 0x2000; // Support for TDLS off channel + public static final int WIFI_FEATURE_EPR = 0x4000; // Enhanced power reporting + + private String name; + private int supportedFeatures; + + public WifiAdapter(String name, int supportedFeatures) { + this.name = name; + this.supportedFeatures = supportedFeatures; + } + + public String getName() { + return name; + } + + private int getSupportedFeatures() { + return supportedFeatures; + } + + private boolean isFeatureSupported(int feature) { + return (supportedFeatures & feature) == feature; + } + + public boolean isPasspointSupported() { + return isFeatureSupported(WIFI_FEATURE_PASSPOINT); + } + + public boolean isWifiDirectSupported() { + return isFeatureSupported(WIFI_FEATURE_P2P); + } + + public boolean isMobileHotstpoSupported() { + return isFeatureSupported(WIFI_FEATURE_MOBILE_HOTSPOT); + } + + public boolean isWifiScannerSupported() { + return isFeatureSupported(WIFI_FEATURE_SCANNER); + } + + public boolean isNanSupported() { + return isFeatureSupported(WIFI_FEATURE_NAN); + } + + public boolean isDeviceToDeviceRttSupported() { + return isFeatureSupported(WIFI_FEATURE_D2D_RTT); + } + + public boolean isDeviceToApRttSupported() { + return isFeatureSupported(WIFI_FEATURE_D2AP_RTT); + } + + public boolean isPreferredNetworkOffloadSupported() { + return isFeatureSupported(WIFI_FEATURE_PNO); + } + + public boolean isAdditionalStaSupported() { + return isFeatureSupported(WIFI_FEATURE_ADDITIONAL_STA); + } + + public boolean isTdlsSupported() { + return isFeatureSupported(WIFI_FEATURE_TDLS); + } + + public boolean isOffChannelTdlsSupported() { + return isFeatureSupported(WIFI_FEATURE_TDLS_OFFCHANNEL); + } + + public boolean isEnhancedPowerReportingSupported() { + return isFeatureSupported(WIFI_FEATURE_EPR); + } + + /* Parcelable implementation */ + + /** Implement the Parcelable interface {@hide} */ + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface {@hide} */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(name); + dest.writeInt(supportedFeatures); + } + + /** Implement the Parcelable interface {@hide} */ + public static final Creator<WifiAdapter> CREATOR = + new Creator<WifiAdapter>() { + public WifiAdapter createFromParcel(Parcel in) { + WifiAdapter adaptor = new WifiAdapter(in.readString(), in.readInt()); + return adaptor; + } + + public WifiAdapter[] newArray(int size) { + return new WifiAdapter[size]; + } + }; +} diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index e99ea358895b..f9a9e7d764d1 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -568,6 +568,17 @@ public class WifiManager { } /** + * @hide + */ + public List<WifiAdapter> getAdaptors() { + try { + return mService.getAdaptors(); + } catch (RemoteException e) { + return null; + } + } + + /** * Return a list of all the networks configured in the supplicant. * Not all fields of WifiConfiguration are returned. Only the following * fields are filled in: diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java index 21b700de3ffc..f3294bb610f9 100644 --- a/wifi/java/android/net/wifi/WifiScanner.java +++ b/wifi/java/android/net/wifi/WifiScanner.java @@ -16,6 +16,7 @@ package android.net.wifi; +import android.annotation.SystemApi; import android.content.Context; import android.os.Handler; import android.os.HandlerThread; @@ -42,6 +43,7 @@ import java.util.concurrent.CountDownLatch; * .WIFI_SCANNING_SERVICE)}. * @hide */ +@SystemApi public class WifiScanner { /** no band specified; use channel list instead */ @@ -61,7 +63,7 @@ public class WifiScanner { public static final int WIFI_BAND_BOTH_WITH_DFS = 7; /* both bands with DFS channels */ /** Minimum supported scanning period */ - public static final int MIN_SCAN_PERIOD_MS = 2000; /* minimum supported period */ + public static final int MIN_SCAN_PERIOD_MS = 1000; /* minimum supported period */ /** Maximum supported scanning period */ public static final int MAX_SCAN_PERIOD_MS = 1024000; /* maximum supported period */ @@ -78,6 +80,7 @@ public class WifiScanner { * Generic action callback invocation interface * @hide */ + @SystemApi public static interface ActionListener { public void onSuccess(); public void onFailure(int reason, String description); @@ -138,7 +141,7 @@ public class WifiScanner { public int band; /** list of channels; used when band is set to WIFI_BAND_UNSPECIFIED */ public ChannelSpec[] channels; - /** period of background scan; in millisecond */ + /** period of background scan; in millisecond, 0 => single shot scan */ public int periodInMs; /** must have a valid REPORT_EVENT value */ public int reportEvents; @@ -267,6 +270,7 @@ public class WifiScanner { /** @hide */ public void scan(ScanSettings settings, ScanListener listener) { validateChannel(); + settings.periodInMs = 0; sAsyncChannel.sendMessage(CMD_SCAN, 0, putListener(listener), settings); } @@ -313,6 +317,7 @@ public class WifiScanner { } /** @hide */ + @SystemApi public static class WifiChangeSettings implements Parcelable { public int rssiSampleSize; /* sample size for RSSI averaging */ public int lostApSampleSize; /* samples to confirm AP's loss */ @@ -443,6 +448,7 @@ public class WifiScanner { } /** @hide */ + @SystemApi public void configureWifiChange(WifiChangeSettings settings) { validateChannel(); sAsyncChannel.sendMessage(CMD_CONFIGURE_WIFI_CHANGE, 0, 0, settings); @@ -457,6 +463,7 @@ public class WifiScanner { } /** @hide */ + @SystemApi public static class HotlistSettings implements Parcelable { public HotspotInfo[] hotspotInfos; public int apLostThreshold; |